diff options
Diffstat (limited to 'gfx/wr/webrender_api/src/display_list.rs')
-rw-r--r-- | gfx/wr/webrender_api/src/display_list.rs | 2338 |
1 files changed, 2338 insertions, 0 deletions
diff --git a/gfx/wr/webrender_api/src/display_list.rs b/gfx/wr/webrender_api/src/display_list.rs new file mode 100644 index 0000000000..f1eea85ca3 --- /dev/null +++ b/gfx/wr/webrender_api/src/display_list.rs @@ -0,0 +1,2338 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * 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 time::precise_time_ns; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +// local imports +use crate::display_item as di; +use crate::display_item_cache::*; +use crate::{APZScrollGeneration, HasScrollLinkedEffect, PipelineId, PropertyBinding}; +use crate::gradient_builder::GradientBuilder; +use crate::color::ColorF; +use crate::font::{FontInstanceKey, GlyphInstance, GlyphOptions}; +use crate::image::{ColorDepth, ImageKey}; +use crate::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 +pub const 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; + +#[derive(Debug, Copy, Clone, PartialEq)] +enum BuildState { + Idle, + Build, +} + +#[repr(C)] +#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct ItemRange<'a, T> { + bytes: &'a [u8], + _boo: PhantomData<T>, +} + +impl<'a, T> Copy for ItemRange<'a, T> {} +impl<'a, T> Clone for ItemRange<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Default for ItemRange<'a, T> { + fn default() -> Self { + ItemRange { + bytes: Default::default(), + _boo: PhantomData, + } + } +} + +impl<'a, T> ItemRange<'a, T> { + pub fn new(bytes: &'a [u8]) -> Self { + Self { + bytes, + _boo: PhantomData + } + } + + pub fn is_empty(&self) -> bool { + // Nothing more than space for a length (0). + self.bytes.len() <= mem::size_of::<usize>() + } + + pub fn bytes(&self) -> &[u8] { + &self.bytes + } +} + +impl<'a, T: Default> ItemRange<'a, T> { + pub fn iter(&self) -> AuxIter<'a, T> { + AuxIter::new(T::default(), self.bytes) + } +} + +impl<'a, T> IntoIterator for ItemRange<'a, T> +where + T: Copy + Default + peek_poke::Peek, +{ + type Item = T; + type IntoIter = AuxIter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +#[derive(Copy, Clone)] +pub struct TempFilterData<'a> { + pub func_types: ItemRange<'a, di::ComponentTransferFuncType>, + pub r_values: ItemRange<'a, f32>, + pub g_values: ItemRange<'a, f32>, + pub b_values: ItemRange<'a, f32>, + pub a_values: ItemRange<'a, f32>, +} + +#[derive(Default, Clone)] +pub struct DisplayListPayload { + /// Serde encoded bytes. Mostly DisplayItems, but some mixed in slices. + pub items_data: Vec<u8>, + + /// Serde encoded DisplayItemCache structs + pub cache_data: Vec<u8>, + + /// Serde encoded SpatialTreeItem structs + pub spatial_tree: Vec<u8>, +} + +impl DisplayListPayload { + fn default() -> Self { + DisplayListPayload { + items_data: Vec::new(), + cache_data: Vec::new(), + spatial_tree: Vec::new(), + } + } + + fn new(capacity: DisplayListCapacity) -> Self { + let mut payload = Self::default(); + + // 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 let Err(_) = payload.items_data.try_reserve(capacity.items_size) { + return Self::default(); + } + if let Err(_) = payload.cache_data.try_reserve(capacity.cache_size) { + return Self::default(); + } + if let Err(_) = payload.spatial_tree.try_reserve(capacity.spatial_tree_size) { + return Self::default(); + } + payload + } + + fn clear(&mut self) { + self.items_data.clear(); + self.cache_data.clear(); + self.spatial_tree.clear(); + } + + fn size_in_bytes(&self) -> usize { + self.items_data.len() + + self.cache_data.len() + + self.spatial_tree.len() + } + + #[cfg(feature = "serialize")] + fn create_debug_spatial_tree_items(&self) -> Vec<di::SpatialTreeItem> { + let mut items = Vec::new(); + + iter_spatial_tree(&self.spatial_tree, |item| { + items.push(*item); + }); + + items + } +} + +impl MallocSizeOf for DisplayListPayload { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.items_data.size_of(ops) + + self.cache_data.size_of(ops) + + self.spatial_tree.size_of(ops) + } +} + +/// A display list. +#[derive(Default, Clone)] +pub struct BuiltDisplayList { + payload: DisplayListPayload, + descriptor: BuiltDisplayListDescriptor, +} + +#[repr(C)] +#[derive(Copy, Clone, Deserialize, Serialize)] +pub enum GeckoDisplayListType { + None, + Partial(f64), + Full(f64), +} + +impl Default for GeckoDisplayListType { + fn default() -> Self { GeckoDisplayListType::None } +} + +/// 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)] +pub struct 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, + /// The size of the cache for this display list. + cache_size: usize, +} + +#[derive(Clone)] +pub struct DisplayListWithCache { + pub display_list: BuiltDisplayList, + cache: DisplayItemCache, +} + +impl DisplayListWithCache { + pub fn iter(&self) -> BuiltDisplayListIter { + self.display_list.iter_with_cache(&self.cache) + } + + pub fn new_from_list(display_list: BuiltDisplayList) -> Self { + let mut cache = DisplayItemCache::new(); + cache.update(&display_list); + + DisplayListWithCache { + display_list, + cache + } + } + + pub fn update(&mut self, display_list: BuiltDisplayList) { + self.cache.update(&display_list); + self.display_list = display_list; + } + + pub fn descriptor(&self) -> &BuiltDisplayListDescriptor { + self.display_list.descriptor() + } + + pub fn times(&self) -> (u64, u64, u64) { + self.display_list.times() + } + + pub fn items_data(&self) -> &[u8] { + self.display_list.items_data() + } +} + +impl MallocSizeOf for DisplayListWithCache { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.display_list.payload.size_of(ops) + self.cache.size_of(ops) + } +} + +/// 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 DisplayListWithCache { + 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.display_list.payload.create_debug_spatial_tree_items(); + + let dl = DisplayListCapture { + display_items, + spatial_tree_items, + descriptor: self.display_list.descriptor, + }; + + dl.serialize(serializer) + } +} + +#[cfg(feature = "deserialize")] +impl<'de> Deserialize<'de> for DisplayListWithCache { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + use crate::display_item::DisplayItem as Real; + use crate::display_item::DebugDisplayItem as Debug; + + let capture = DisplayListCapture::deserialize(deserializer)?; + + let mut 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); + + let mut items_data = Vec::new(); + let mut temp = Vec::new(); + for complete in capture.display_items { + let item = match complete { + Debug::ClipChain(v, clip_chain_ids) => { + DisplayListBuilder::push_iter_impl(&mut temp, clip_chain_ids); + Real::ClipChain(v) + } + Debug::Text(v, glyphs) => { + DisplayListBuilder::push_iter_impl(&mut temp, glyphs); + Real::Text(v) + }, + Debug::Iframe(v) => { + Real::Iframe(v) + } + Debug::PushReferenceFrame(v) => { + Real::PushReferenceFrame(v) + } + Debug::SetFilterOps(filters) => { + DisplayListBuilder::push_iter_impl(&mut temp, filters); + Real::SetFilterOps + }, + Debug::SetFilterData(filter_data) => { + let func_types: Vec<di::ComponentTransferFuncType> = + [filter_data.func_r_type, + filter_data.func_g_type, + filter_data.func_b_type, + filter_data.func_a_type].to_vec(); + DisplayListBuilder::push_iter_impl(&mut temp, func_types); + DisplayListBuilder::push_iter_impl(&mut temp, filter_data.r_values); + DisplayListBuilder::push_iter_impl(&mut temp, filter_data.g_values); + DisplayListBuilder::push_iter_impl(&mut temp, filter_data.b_values); + DisplayListBuilder::push_iter_impl(&mut temp, filter_data.a_values); + Real::SetFilterData + }, + Debug::SetFilterPrimitives(filter_primitives) => { + DisplayListBuilder::push_iter_impl(&mut temp, filter_primitives); + Real::SetFilterPrimitives + } + Debug::SetGradientStops(stops) => { + DisplayListBuilder::push_iter_impl(&mut temp, stops); + Real::SetGradientStops + }, + Debug::SetPoints(points) => { + DisplayListBuilder::push_iter_impl(&mut temp, points); + Real::SetPoints + }, + Debug::RectClip(v) => Real::RectClip(v), + Debug::RoundedRectClip(v) => Real::RoundedRectClip(v), + Debug::ImageMaskClip(v) => Real::ImageMaskClip(v), + Debug::Rectangle(v) => Real::Rectangle(v), + Debug::ClearRectangle(v) => Real::ClearRectangle(v), + Debug::HitTest(v) => Real::HitTest(v), + Debug::Line(v) => Real::Line(v), + Debug::Image(v) => Real::Image(v), + Debug::RepeatingImage(v) => Real::RepeatingImage(v), + Debug::YuvImage(v) => Real::YuvImage(v), + Debug::Border(v) => Real::Border(v), + Debug::BoxShadow(v) => Real::BoxShadow(v), + Debug::Gradient(v) => Real::Gradient(v), + Debug::RadialGradient(v) => Real::RadialGradient(v), + Debug::ConicGradient(v) => Real::ConicGradient(v), + Debug::PushStackingContext(v) => Real::PushStackingContext(v), + Debug::PushShadow(v) => Real::PushShadow(v), + Debug::BackdropFilter(v) => Real::BackdropFilter(v), + + Debug::PopStackingContext => Real::PopStackingContext, + Debug::PopReferenceFrame => Real::PopReferenceFrame, + Debug::PopAllShadows => Real::PopAllShadows, + }; + 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); + + Ok(DisplayListWithCache { + display_list: BuiltDisplayList { + descriptor: capture.descriptor, + payload: DisplayListPayload { + cache_data: Vec::new(), + items_data, + spatial_tree, + }, + }, + cache: DisplayItemCache::new(), + }) + } +} + +pub struct BuiltDisplayListIter<'a> { + data: &'a [u8], + cache: Option<&'a DisplayItemCache>, + pending_items: std::slice::Iter<'a, CachedDisplayItem>, + cur_cached_item: Option<&'a CachedDisplayItem>, + 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_filter_primitives: ItemRange<'a, di::FilterPrimitive>, + 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>, +} + +impl DebugStats { + #[cfg(feature = "display_list_stats")] + fn _update_entry(&mut self, name: &'static str, item_count: usize, byte_count: usize) { + let entry = self.stats.entry(name).or_default(); + entry.total_count += item_count; + entry.num_bytes += byte_count; + } + + /// 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(&mut self, 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(&mut self, 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>( + &mut self, + 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(); + + self._update_entry(slice_name, range.iter().len(), range.bytes.len()); + } + + #[cfg(not(feature = "display_list_stats"))] + fn log_slice<T>(&mut self, _slice_name: &str, _range: &ItemRange<T>) { + /* no-op */ + } +} + +/// Stats for an individual item +#[derive(Copy, Clone, Debug, Default)] +pub struct 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, +} + +pub struct DisplayItemRef<'a: 'b, 'b> { + iter: &'b BuiltDisplayListIter<'a>, +} + +// 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. + pub fn sub_iter(&self) -> BuiltDisplayListIter<'a> { + self.iter.sub_iter() + } + + pub fn item(&self) -> &di::DisplayItem { + self.iter.current_item() + } + + pub fn clip_chain_items(&self) -> ItemRange<di::ClipId> { + self.iter.cur_clip_chain_items + } + + pub fn points(&self) -> ItemRange<LayoutPoint> { + self.iter.cur_points + } + + pub fn glyphs(&self) -> ItemRange<GlyphInstance> { + self.iter.glyphs() + } + + pub fn gradient_stops(&self) -> ItemRange<di::GradientStop> { + self.iter.gradient_stops() + } + + pub fn filters(&self) -> ItemRange<di::FilterOp> { + self.iter.cur_filters + } + + pub fn filter_datas(&self) -> &Vec<TempFilterData> { + &self.iter.cur_filter_data + } + + pub fn filter_primitives(&self) -> ItemRange<di::FilterPrimitive> { + self.iter.cur_filter_primitives + } +} + +#[derive(PartialEq)] +enum Peek { + StartPeeking, + IsPeeking, + NotPeeking, +} + +#[derive(Clone)] +pub struct AuxIter<'a, T> { + item: T, + data: &'a [u8], + size: usize, +// _boo: PhantomData<T>, +} + +impl BuiltDisplayList { + pub fn from_data( + payload: DisplayListPayload, + descriptor: BuiltDisplayListDescriptor, + ) -> Self { + BuiltDisplayList { + payload, + descriptor, + } + } + + pub fn into_data(self) -> (DisplayListPayload, BuiltDisplayListDescriptor) { + (self.payload, self.descriptor) + } + + pub fn items_data(&self) -> &[u8] { + &self.payload.items_data + } + + pub fn cache_data(&self) -> &[u8] { + &self.payload.cache_data + } + + pub fn descriptor(&self) -> &BuiltDisplayListDescriptor { + &self.descriptor + } + + pub fn set_send_time_ns(&mut self, time: u64) { + self.descriptor.send_start_time = time; + } + + pub fn times(&self) -> (u64, u64, u64) { + ( + self.descriptor.builder_start_time, + self.descriptor.builder_finish_time, + self.descriptor.send_start_time, + ) + } + + pub fn gecko_display_list_stats(&self) -> (f64, bool) { + match self.descriptor.gecko_display_list_type { + GeckoDisplayListType::Full(duration) => (duration, true), + GeckoDisplayListType::Partial(duration) => (duration, false), + _ => (0.0, false) + } + } + + pub fn total_clip_nodes(&self) -> usize { + self.descriptor.total_clip_nodes + } + + pub fn total_spatial_nodes(&self) -> usize { + self.descriptor.total_spatial_nodes + } + + pub fn iter(&self) -> BuiltDisplayListIter { + BuiltDisplayListIter::new(self.items_data(), None) + } + + pub fn cache_data_iter(&self) -> BuiltDisplayListIter { + BuiltDisplayListIter::new(self.cache_data(), None) + } + + pub fn iter_with_cache<'a>( + &'a self, + cache: &'a DisplayItemCache + ) -> BuiltDisplayListIter<'a> { + BuiltDisplayListIter::new(self.items_data(), Some(cache)) + } + + pub fn cache_size(&self) -> usize { + self.descriptor.cache_size + } + + pub fn size_in_bytes(&self) -> usize { + self.payload.size_in_bytes() + } + + pub fn iter_spatial_tree<F>(&self, f: F) where F: FnMut(&di::SpatialTreeItem) { + iter_spatial_tree(&self.payload.spatial_tree, f) + } + + #[cfg(feature = "serialize")] + pub fn create_debug_display_items( + mut iterator: BuiltDisplayListIter, + ) -> Vec<di::DebugDisplayItem> { + use di::DisplayItem as Real; + use di::DebugDisplayItem as Debug; + let mut debug_items = Vec::new(); + + while let Some(item) = iterator.next_raw() { + let serial_di = match *item.item() { + Real::ClipChain(v) => Debug::ClipChain( + v, + item.iter.cur_clip_chain_items.iter().collect() + ), + Real::Text(v) => Debug::Text( + v, + item.iter.cur_glyphs.iter().collect() + ), + Real::SetFilterOps => Debug::SetFilterOps( + item.iter.cur_filters.iter().collect() + ), + Real::SetFilterData => { + debug_assert!(!item.iter.cur_filter_data.is_empty(), + "next_raw should have populated cur_filter_data"); + let temp_filter_data = &item.iter.cur_filter_data[item.iter.cur_filter_data.len()-1]; + + let func_types: Vec<di::ComponentTransferFuncType> = + temp_filter_data.func_types.iter().collect(); + debug_assert!(func_types.len() == 4, + "someone changed the number of filter funcs without updating this code"); + Debug::SetFilterData(di::FilterData { + func_r_type: func_types[0], + r_values: temp_filter_data.r_values.iter().collect(), + func_g_type: func_types[1], + g_values: temp_filter_data.g_values.iter().collect(), + func_b_type: func_types[2], + b_values: temp_filter_data.b_values.iter().collect(), + func_a_type: func_types[3], + a_values: temp_filter_data.a_values.iter().collect(), + }) + }, + Real::SetFilterPrimitives => Debug::SetFilterPrimitives( + item.iter.cur_filter_primitives.iter().collect() + ), + Real::SetGradientStops => Debug::SetGradientStops( + item.iter.cur_stops.iter().collect() + ), + Real::SetPoints => Debug::SetPoints( + item.iter.cur_points.iter().collect() + ), + Real::RectClip(v) => Debug::RectClip(v), + Real::RoundedRectClip(v) => Debug::RoundedRectClip(v), + Real::ImageMaskClip(v) => Debug::ImageMaskClip(v), + Real::Rectangle(v) => Debug::Rectangle(v), + Real::ClearRectangle(v) => Debug::ClearRectangle(v), + Real::HitTest(v) => Debug::HitTest(v), + Real::Line(v) => Debug::Line(v), + Real::Image(v) => Debug::Image(v), + Real::RepeatingImage(v) => Debug::RepeatingImage(v), + Real::YuvImage(v) => Debug::YuvImage(v), + Real::Border(v) => Debug::Border(v), + Real::BoxShadow(v) => Debug::BoxShadow(v), + Real::Gradient(v) => Debug::Gradient(v), + Real::RadialGradient(v) => Debug::RadialGradient(v), + Real::ConicGradient(v) => Debug::ConicGradient(v), + Real::Iframe(v) => Debug::Iframe(v), + Real::PushReferenceFrame(v) => Debug::PushReferenceFrame(v), + Real::PushStackingContext(v) => Debug::PushStackingContext(v), + Real::PushShadow(v) => Debug::PushShadow(v), + Real::BackdropFilter(v) => Debug::BackdropFilter(v), + + Real::PopReferenceFrame => Debug::PopReferenceFrame, + Real::PopStackingContext => Debug::PopStackingContext, + Real::PopAllShadows => Debug::PopAllShadows, + Real::ReuseItems(_) | + Real::RetainedItems(_) => unreachable!("Unexpected item"), + }; + debug_items.push(serial_di); + } + + debug_items + } +} + +/// Returns the byte-range the slice occupied. +fn skip_slice<'a, T: peek_poke::Peek>(data: &mut &'a [u8]) -> ItemRange<'a, T> { + let mut skip_offset = 0usize; + *data = peek_from_slice(data, &mut skip_offset); + let (skip, rest) = data.split_at(skip_offset); + + // Adjust data pointer to skip read values + *data = rest; + + ItemRange { + bytes: skip, + _boo: PhantomData, + } +} + +impl<'a> BuiltDisplayListIter<'a> { + pub fn new( + data: &'a [u8], + cache: Option<&'a DisplayItemCache>, + ) -> Self { + Self { + data, + cache, + pending_items: [].iter(), + cur_cached_item: None, + cur_item: di::DisplayItem::PopStackingContext, + cur_stops: ItemRange::default(), + cur_glyphs: ItemRange::default(), + cur_filters: ItemRange::default(), + cur_filter_data: Vec::new(), + cur_filter_primitives: ItemRange::default(), + cur_clip_chain_items: ItemRange::default(), + cur_points: ItemRange::default(), + peeking: Peek::NotPeeking, + debug_stats: DebugStats { + last_addr: data.as_ptr() as usize, + stats: HashMap::default(), + }, + } + } + + pub fn sub_iter(&self) -> Self { + let mut iter = BuiltDisplayListIter::new( + self.data, self.cache + ); + iter.pending_items = self.pending_items.clone(); + iter + } + + pub fn current_item(&self) -> &di::DisplayItem { + match self.cur_cached_item { + Some(cached_item) => cached_item.display_item(), + None => &self.cur_item + } + } + + fn cached_item_range_or<T>( + &self, + data: ItemRange<'a, T> + ) -> ItemRange<'a, T> { + match self.cur_cached_item { + Some(cached_item) => cached_item.data_as_item_range(), + None => data, + } + } + + pub fn glyphs(&self) -> ItemRange<GlyphInstance> { + self.cached_item_range_or(self.cur_glyphs) + } + + pub fn gradient_stops(&self) -> ItemRange<di::GradientStop> { + self.cached_item_range_or(self.cur_stops) + } + + fn advance_pending_items(&mut self) -> bool { + self.cur_cached_item = self.pending_items.next(); + self.cur_cached_item.is_some() + } + + pub fn next<'b>(&'b mut self) -> Option<DisplayItemRef<'a, 'b>> { + use crate::DisplayItem::*; + + match self.peeking { + Peek::IsPeeking => { + self.peeking = Peek::NotPeeking; + return Some(self.as_ref()); + } + Peek::StartPeeking => { + self.peeking = Peek::IsPeeking; + } + Peek::NotPeeking => { /* do nothing */ } + } + + // 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_primitives = ItemRange::default(); + self.cur_filter_data.clear(); + + loop { + self.next_raw()?; + match self.cur_item { + SetGradientStops | + SetFilterOps | + SetFilterData | + SetFilterPrimitives | + 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). + pub fn next_raw<'b>(&'b mut self) -> Option<DisplayItemRef<'a, 'b>> { + use crate::DisplayItem::*; + + if self.advance_pending_items() { + return Some(self.as_ref()); + } + + // 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. + if self.data.len() <= di::DisplayItem::max_size() { + return None; + } + + self.data = peek_from_slice(self.data, &mut self.cur_item); + self.log_item_stats(); + + match self.cur_item { + SetGradientStops => { + self.cur_stops = skip_slice::<di::GradientStop>(&mut self.data); + self.debug_stats.log_slice("set_gradient_stops.stops", &self.cur_stops); + } + SetFilterOps => { + self.cur_filters = skip_slice::<di::FilterOp>(&mut self.data); + self.debug_stats.log_slice("set_filter_ops.ops", &self.cur_filters); + } + SetFilterData => { + self.cur_filter_data.push(TempFilterData { + func_types: skip_slice::<di::ComponentTransferFuncType>(&mut self.data), + r_values: skip_slice::<f32>(&mut self.data), + g_values: skip_slice::<f32>(&mut self.data), + b_values: skip_slice::<f32>(&mut self.data), + a_values: skip_slice::<f32>(&mut self.data), + }); + + let data = *self.cur_filter_data.last().unwrap(); + self.debug_stats.log_slice("set_filter_data.func_types", &data.func_types); + self.debug_stats.log_slice("set_filter_data.r_values", &data.r_values); + self.debug_stats.log_slice("set_filter_data.g_values", &data.g_values); + self.debug_stats.log_slice("set_filter_data.b_values", &data.b_values); + self.debug_stats.log_slice("set_filter_data.a_values", &data.a_values); + } + SetFilterPrimitives => { + self.cur_filter_primitives = skip_slice::<di::FilterPrimitive>(&mut self.data); + self.debug_stats.log_slice("set_filter_primitives.primitives", &self.cur_filter_primitives); + } + SetPoints => { + self.cur_points = skip_slice::<LayoutPoint>(&mut self.data); + self.debug_stats.log_slice("set_points.points", &self.cur_points); + } + ClipChain(_) => { + self.cur_clip_chain_items = skip_slice::<di::ClipId>(&mut self.data); + self.debug_stats.log_slice("clip_chain.clip_ids", &self.cur_clip_chain_items); + } + Text(_) => { + self.cur_glyphs = skip_slice::<GlyphInstance>(&mut self.data); + self.debug_stats.log_slice("text.glyphs", &self.cur_glyphs); + } + ReuseItems(key) => { + match self.cache { + Some(cache) => { + self.pending_items = cache.get_items(key).iter(); + self.advance_pending_items(); + } + None => { + unreachable!("Cache marker without cache!"); + } + } + } + _ => { /* do nothing */ } + } + + Some(self.as_ref()) + } + + pub fn as_ref<'b>(&'b self) -> DisplayItemRef<'a, 'b> { + DisplayItemRef { + iter: self, + } + } + + pub fn skip_current_stacking_context(&mut self) { + let mut depth = 0; + while let Some(item) = self.next() { + match *item.item() { + di::DisplayItem::PushStackingContext(..) => depth += 1, + di::DisplayItem::PopStackingContext if depth == 0 => return, + di::DisplayItem::PopStackingContext => depth -= 1, + _ => {} + } + } + } + + pub fn current_stacking_context_empty(&mut self) -> bool { + match self.peek() { + Some(item) => *item.item() == di::DisplayItem::PopStackingContext, + None => true, + } + } + + pub fn peek<'b>(&'b mut self) -> Option<DisplayItemRef<'a, 'b>> { + if self.peeking == Peek::NotPeeking { + self.peeking = Peek::StartPeeking; + self.next() + } else { + Some(self.as_ref()) + } + } + + /// Get the debug stats for what this iterator has deserialized. + /// Should always be empty in release builds. + pub fn debug_stats(&mut self) -> Vec<(&'static str, ItemStats)> { + let mut 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). + pub fn merge_debug_stats_from(&mut self, other: &mut Self) { + for (key, other_entry) in other.debug_stats.stats.iter() { + let entry = self.debug_stats.stats.entry(key).or_default(); + + entry.total_count += other_entry.total_count; + entry.num_bytes += other_entry.num_bytes; + } + } + + /// Logs stats for the last deserialized display item + #[cfg(feature = "display_list_stats")] + fn log_item_stats(&mut self) { + self.debug_stats.log_item(self.data, &self.cur_item); + } + + #[cfg(not(feature = "display_list_stats"))] + fn log_item_stats(&mut self) { /* no-op */ } +} + +impl<'a, T> AuxIter<'a, T> { + pub fn new(item: T, mut data: &'a [u8]) -> Self { + let mut size = 0usize; + if !data.is_empty() { + data = peek_from_slice(data, &mut size); + }; + + AuxIter { + item, + data, + size, +// _boo: PhantomData, + } + } +} + +impl<'a, T: Copy + peek_poke::Peek> Iterator for AuxIter<'a, T> { + type Item = T; + + fn next(&mut self) -> Option<Self::Item> { + if self.size == 0 { + None + } else { + self.size -= 1; + self.data = peek_from_slice(self.data, &mut self.item); + Some(self.item) + } + } + + fn size_hint(&self) -> (usize, Option<usize>) { + (self.size, Some(self.size)) + } +} + +impl<'a, T: Copy + peek_poke::Peek> ::std::iter::ExactSizeIterator for AuxIter<'a, T> {} + +#[derive(Clone, Debug)] +pub struct SaveState { + dl_items_len: usize, + dl_cache_len: usize, + next_clip_index: usize, + next_spatial_index: usize, + next_clip_chain_id: u64, +} + +/// DisplayListSection determines the target buffer for the display items. +pub enum DisplayListSection { + /// The main/default buffer: contains item data and item group markers. + Data, + /// Auxiliary buffer: contains the item data for item groups. + CacheData, + /// Temporary buffer: contains the data for pending item group. Flushed to + /// one of the buffers above, after item grouping finishes. + Chunk, +} + +/// A small portion of a normal spatial node that we store during DL construction to +/// enable snapping and reference frame <-> stacking context coord mapping. In future +/// we'll aim to remove this and have the full spatial tree available during DL build. +#[derive(Clone)] +pub struct SpatialNodeInfo { + /// The total external scroll offset applicable at this node + accumulated_external_scroll_offset: LayoutVector2D, +} + +impl SpatialNodeInfo { + fn identity() -> Self { + SpatialNodeInfo { + accumulated_external_scroll_offset: LayoutVector2D::zero(), + } + } +} + +pub struct DisplayListBuilder { + payload: DisplayListPayload, + pub pipeline_id: PipelineId, + + pending_chunk: Vec<u8>, + writing_to_chunk: bool, + + next_clip_index: usize, + next_spatial_index: usize, + next_clip_chain_id: u64, + builder_start_time: u64, + + save_state: Option<SaveState>, + + cache_size: usize, + serialized_content_buffer: Option<String>, + state: BuildState, + + /// Helper struct to map stacking context coords <-> reference frame coords. + rf_mapper: ReferenceFrameMapper, + + /// Minimal info about encountered spatial nodes to allow snapping during DL building + spatial_nodes: Vec<SpatialNodeInfo>, +} + +#[repr(C)] +struct DisplayListCapacity { + items_size: usize, + cache_size: usize, + spatial_tree_size: usize, +} + +impl DisplayListCapacity { + fn empty() -> Self { + DisplayListCapacity { + items_size: 0, + cache_size: 0, + spatial_tree_size: 0, + } + } +} + +impl DisplayListBuilder { + pub fn new(pipeline_id: PipelineId) -> Self { + DisplayListBuilder { + payload: DisplayListPayload::new(DisplayListCapacity::empty()), + pipeline_id, + + pending_chunk: Vec::new(), + writing_to_chunk: false, + + next_clip_index: FIRST_CLIP_NODE_INDEX, + next_spatial_index: FIRST_SPATIAL_NODE_INDEX, + next_clip_chain_id: 0, + builder_start_time: 0, + save_state: None, + cache_size: 0, + serialized_content_buffer: None, + state: BuildState::Idle, + + rf_mapper: ReferenceFrameMapper::new(), + spatial_nodes: vec![SpatialNodeInfo::identity(); FIRST_SPATIAL_NODE_INDEX + 1], + } + } + + fn reset(&mut self) { + self.payload.clear(); + self.pending_chunk.clear(); + self.writing_to_chunk = false; + + self.next_clip_index = FIRST_CLIP_NODE_INDEX; + self.next_spatial_index = FIRST_SPATIAL_NODE_INDEX; + self.next_clip_chain_id = 0; + + self.save_state = None; + self.cache_size = 0; + self.serialized_content_buffer = None; + + self.rf_mapper = ReferenceFrameMapper::new(); + self.spatial_nodes = vec![SpatialNodeInfo::identity(); FIRST_SPATIAL_NODE_INDEX + 1]; + } + + /// 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. + pub fn save(&mut self) { + assert!(self.save_state.is_none(), "DisplayListBuilder doesn't support nested saves"); + + self.save_state = Some(SaveState { + dl_items_len: self.payload.items_data.len(), + dl_cache_len: self.payload.cache_data.len(), + next_clip_index: self.next_clip_index, + next_spatial_index: self.next_spatial_index, + next_clip_chain_id: self.next_clip_chain_id, + }); + } + + /// Restores the state of the builder to when `save()` was last called. + pub fn restore(&mut self) { + let state = self.save_state.take().expect("No save to restore DisplayListBuilder from"); + + self.payload.items_data.truncate(state.dl_items_len); + self.payload.cache_data.truncate(state.dl_cache_len); + self.next_clip_index = state.next_clip_index; + self.next_spatial_index = state.next_spatial_index; + self.next_clip_chain_id = state.next_clip_chain_id; + } + + /// Discards the builder's save (indicating the attempted operation was successful). + pub fn clear_save(&mut self) { + 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. + pub fn emit_display_list<W>( + &mut self, + indent: usize, + range: Range<Option<usize>>, + mut sink: W, + ) -> usize + where + W: Write + { + let mut temp = BuiltDisplayList::default(); + ensure_red_zone::<di::DisplayItem>(&mut self.payload.items_data); + ensure_red_zone::<di::DisplayItem>(&mut self.payload.cache_data); + mem::swap(&mut temp.payload, &mut self.payload); + + let mut index: usize = 0; + { + let mut cache = DisplayItemCache::new(); + cache.update(&temp); + let mut iter = temp.iter_with_cache(&cache); + while let 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>(&mut self.payload.items_data); + strip_red_zone::<di::DisplayItem>(&mut self.payload.cache_data); + index + } + + /// Print the display items in the list to stdout. + pub fn dump_serialized_display_list(&mut self) { + self.serialized_content_buffer = Some(String::new()); + } + + fn add_to_display_list_dump<T: std::fmt::Debug>(&mut self, item: T) { + if let Some(ref mut content) = self.serialized_content_buffer { + use std::fmt::Write; + write!(content, "{:?}\n", item).expect("DL dump write failed."); + } + } + + /// Returns the default section that DisplayListBuilder will write to, + /// if no section is specified explicitly. + fn default_section(&self) -> DisplayListSection { + if self.writing_to_chunk { + DisplayListSection::Chunk + } else { + DisplayListSection::Data + } + } + + fn buffer_from_section( + &mut self, + section: DisplayListSection + ) -> &mut Vec<u8> { + match section { + DisplayListSection::Data => &mut self.payload.items_data, + DisplayListSection::CacheData => &mut self.payload.cache_data, + DisplayListSection::Chunk => &mut self.pending_chunk, + } + } + + #[inline] + pub fn push_item_to_section( + &mut self, + item: &di::DisplayItem, + section: DisplayListSection, + ) { + debug_assert_eq!(self.state, BuildState::Build); + poke_into_vec(item, self.buffer_from_section(section)); + self.add_to_display_list_dump(item); + } + + /// 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] + pub fn push_item(&mut self, item: &di::DisplayItem) { + self.push_item_to_section(item, self.default_section()); + } + + #[inline] + pub fn push_spatial_tree_item(&mut self, item: &di::SpatialTreeItem) { + debug_assert_eq!(self.state, BuildState::Build); + poke_into_vec(item, &mut self.payload.spatial_tree); + } + + 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. + pub fn push_iter<I>(&mut self, iter: I) + where + I: IntoIterator, + I::IntoIter: ExactSizeIterator, + I::Item: Poke, + { + assert_eq!(self.state, BuildState::Build); + + let mut buffer = self.buffer_from_section(self.default_section()); + Self::push_iter_impl(&mut buffer, iter); + } + + pub fn push_rect( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + color: ColorF, + ) { + let item = di::DisplayItem::Rectangle(di::RectangleDisplayItem { + common: *common, + color: PropertyBinding::Value(color), + bounds, + }); + self.push_item(&item); + } + + pub fn push_rect_with_animation( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + color: PropertyBinding<ColorF>, + ) { + let item = di::DisplayItem::Rectangle(di::RectangleDisplayItem { + common: *common, + color, + bounds, + }); + self.push_item(&item); + } + + pub fn push_clear_rect( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + ) { + let item = di::DisplayItem::ClearRectangle(di::ClearRectangleDisplayItem { + common: *common, + bounds, + }); + self.push_item(&item); + } + + pub fn push_hit_test( + &mut self, + rect: LayoutRect, + clip_chain_id: di::ClipChainId, + spatial_id: di::SpatialId, + flags: di::PrimitiveFlags, + tag: di::ItemTag, + ) { + let item = di::DisplayItem::HitTest(di::HitTestDisplayItem { + rect, + clip_chain_id, + spatial_id, + flags, + tag, + }); + self.push_item(&item); + } + + pub fn push_line( + &mut self, + common: &di::CommonItemProperties, + area: &LayoutRect, + wavy_line_thickness: f32, + orientation: di::LineOrientation, + color: &ColorF, + style: di::LineStyle, + ) { + let item = di::DisplayItem::Line(di::LineDisplayItem { + common: *common, + area: *area, + wavy_line_thickness, + orientation, + color: *color, + style, + }); + + self.push_item(&item); + } + + pub fn push_image( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + image_rendering: di::ImageRendering, + alpha_type: di::AlphaType, + key: ImageKey, + color: ColorF, + ) { + let item = di::DisplayItem::Image(di::ImageDisplayItem { + common: *common, + bounds, + image_key: key, + image_rendering, + alpha_type, + color, + }); + + self.push_item(&item); + } + + pub fn push_repeating_image( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + stretch_size: LayoutSize, + tile_spacing: LayoutSize, + image_rendering: di::ImageRendering, + alpha_type: di::AlphaType, + key: ImageKey, + color: ColorF, + ) { + let item = di::DisplayItem::RepeatingImage(di::RepeatingImageDisplayItem { + common: *common, + bounds, + image_key: key, + stretch_size, + tile_spacing, + image_rendering, + alpha_type, + color, + }); + + self.push_item(&item); + } + + /// Push a yuv image. All planar data in yuv image should use the same buffer type. + pub fn push_yuv_image( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + yuv_data: di::YuvData, + color_depth: ColorDepth, + color_space: di::YuvColorSpace, + color_range: di::ColorRange, + image_rendering: di::ImageRendering, + ) { + let item = di::DisplayItem::YuvImage(di::YuvImageDisplayItem { + common: *common, + bounds, + yuv_data, + color_depth, + color_space, + color_range, + image_rendering, + }); + self.push_item(&item); + } + + pub fn push_text( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + glyphs: &[GlyphInstance], + font_key: FontInstanceKey, + color: ColorF, + glyph_options: Option<GlyphOptions>, + ) { + let item = di::DisplayItem::Text(di::TextDisplayItem { + common: *common, + bounds, + color, + font_key, + glyph_options, + }); + + for split_glyphs in glyphs.chunks(MAX_TEXT_RUN_LENGTH) { + self.push_item(&item); + self.push_iter(split_glyphs); + } + } + + /// NOTE: gradients must be pushed in the order they're created + /// because create_gradient stores the stops in anticipation. + pub fn create_gradient( + &mut self, + start_point: LayoutPoint, + end_point: LayoutPoint, + stops: Vec<di::GradientStop>, + extend_mode: di::ExtendMode, + ) -> di::Gradient { + let mut 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. + pub fn create_radial_gradient( + &mut self, + center: LayoutPoint, + radius: LayoutSize, + stops: Vec<di::GradientStop>, + extend_mode: di::ExtendMode, + ) -> di::RadialGradient { + let mut 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. + pub fn create_conic_gradient( + &mut self, + center: LayoutPoint, + angle: f32, + stops: Vec<di::GradientStop>, + extend_mode: di::ExtendMode, + ) -> di::ConicGradient { + let mut builder = GradientBuilder::with_stops(stops); + let gradient = builder.conic_gradient(center, angle, extend_mode); + self.push_stops(builder.stops()); + gradient + } + + pub fn push_border( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + widths: LayoutSideOffsets, + details: di::BorderDetails, + ) { + let item = di::DisplayItem::Border(di::BorderDisplayItem { + common: *common, + bounds, + details, + widths, + }); + + self.push_item(&item); + } + + pub fn push_box_shadow( + &mut self, + common: &di::CommonItemProperties, + box_bounds: LayoutRect, + offset: LayoutVector2D, + color: ColorF, + blur_radius: f32, + spread_radius: f32, + border_radius: di::BorderRadius, + clip_mode: di::BoxShadowClipMode, + ) { + let item = di::DisplayItem::BoxShadow(di::BoxShadowDisplayItem { + common: *common, + box_bounds, + offset, + color, + blur_radius, + spread_radius, + border_radius, + clip_mode, + }); + + self.push_item(&item); + } + + /// 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. + pub fn push_gradient( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + gradient: di::Gradient, + tile_size: LayoutSize, + tile_spacing: LayoutSize, + ) { + let item = di::DisplayItem::Gradient(di::GradientDisplayItem { + common: *common, + bounds, + gradient, + tile_size, + tile_spacing, + }); + + self.push_item(&item); + } + + /// Pushes a radial gradient to be displayed. + /// + /// See [`push_gradient`](#method.push_gradient) for explanation. + pub fn push_radial_gradient( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + gradient: di::RadialGradient, + tile_size: LayoutSize, + tile_spacing: LayoutSize, + ) { + let item = di::DisplayItem::RadialGradient(di::RadialGradientDisplayItem { + common: *common, + bounds, + gradient, + tile_size, + tile_spacing, + }); + + self.push_item(&item); + } + + /// Pushes a conic gradient to be displayed. + /// + /// See [`push_gradient`](#method.push_gradient) for explanation. + pub fn push_conic_gradient( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + gradient: di::ConicGradient, + tile_size: LayoutSize, + tile_spacing: LayoutSize, + ) { + let item = di::DisplayItem::ConicGradient(di::ConicGradientDisplayItem { + common: *common, + bounds, + gradient, + tile_size, + tile_spacing, + }); + + self.push_item(&item); + } + + pub fn push_reference_frame( + &mut self, + origin: LayoutPoint, + parent_spatial_id: di::SpatialId, + transform_style: di::TransformStyle, + transform: PropertyBinding<LayoutTransform>, + kind: di::ReferenceFrameKind, + key: di::SpatialTreeItemKey, + ) -> di::SpatialId { + let id = self.generate_spatial_index(); + + let current_offset = self.current_offset(parent_spatial_id); + let origin = origin + current_offset; + + self.add_spatial_node_info( + id, + LayoutVector2D::zero(), + ); + + let descriptor = di::SpatialTreeItem::ReferenceFrame(di::ReferenceFrameDescriptor { + parent_spatial_id, + origin, + reference_frame: di::ReferenceFrame { + transform_style, + transform: di::ReferenceTransformBinding::Static { + binding: transform, + }, + kind, + id, + key, + }, + }); + self.push_spatial_tree_item(&descriptor); + + self.rf_mapper.push_scope(); + + let item = di::DisplayItem::PushReferenceFrame(di::ReferenceFrameDisplayListItem { + }); + self.push_item(&item); + + id + } + + pub fn push_computed_frame( + &mut self, + origin: LayoutPoint, + parent_spatial_id: di::SpatialId, + scale_from: Option<LayoutSize>, + vertical_flip: bool, + rotation: di::Rotation, + key: di::SpatialTreeItemKey, + ) -> di::SpatialId { + let id = self.generate_spatial_index(); + + let current_offset = self.current_offset(parent_spatial_id); + let origin = origin + current_offset; + + let descriptor = di::SpatialTreeItem::ReferenceFrame(di::ReferenceFrameDescriptor { + parent_spatial_id, + origin, + reference_frame: di::ReferenceFrame { + transform_style: di::TransformStyle::Flat, + transform: di::ReferenceTransformBinding::Computed { + scale_from, + vertical_flip, + rotation, + }, + kind: di::ReferenceFrameKind::Transform { + is_2d_scale_translation: false, + should_snap: false, + paired_with_perspective: false, + }, + id, + key, + }, + }); + self.push_spatial_tree_item(&descriptor); + + self.rf_mapper.push_scope(); + + let item = di::DisplayItem::PushReferenceFrame(di::ReferenceFrameDisplayListItem { + }); + self.push_item(&item); + + id + } + + pub fn pop_reference_frame(&mut self) { + self.rf_mapper.pop_scope(); + self.push_item(&di::DisplayItem::PopReferenceFrame); + } + + pub fn push_stacking_context( + &mut self, + origin: LayoutPoint, + spatial_id: di::SpatialId, + prim_flags: di::PrimitiveFlags, + clip_chain_id: Option<di::ClipChainId>, + transform_style: di::TransformStyle, + mix_blend_mode: di::MixBlendMode, + filters: &[di::FilterOp], + filter_datas: &[di::FilterData], + filter_primitives: &[di::FilterPrimitive], + raster_space: di::RasterSpace, + flags: di::StackingContextFlags, + ) { + self.push_filters(filters, filter_datas, filter_primitives); + + let item = di::DisplayItem::PushStackingContext(di::PushStackingContextDisplayItem { + origin, + spatial_id, + prim_flags, + stacking_context: di::StackingContext { + transform_style, + mix_blend_mode, + clip_chain_id, + raster_space, + flags, + }, + }); + + self.rf_mapper.push_offset(origin.to_vector()); + self.push_item(&item); + } + + /// Helper for examples/ code. + pub fn push_simple_stacking_context( + &mut self, + origin: LayoutPoint, + spatial_id: di::SpatialId, + prim_flags: di::PrimitiveFlags, + ) { + self.push_simple_stacking_context_with_filters( + origin, + spatial_id, + prim_flags, + &[], + &[], + &[], + ); + } + + /// Helper for examples/ code. + pub fn push_simple_stacking_context_with_filters( + &mut self, + origin: LayoutPoint, + spatial_id: di::SpatialId, + prim_flags: di::PrimitiveFlags, + filters: &[di::FilterOp], + filter_datas: &[di::FilterData], + filter_primitives: &[di::FilterPrimitive], + ) { + self.push_stacking_context( + origin, + spatial_id, + prim_flags, + None, + di::TransformStyle::Flat, + di::MixBlendMode::Normal, + filters, + filter_datas, + filter_primitives, + di::RasterSpace::Screen, + di::StackingContextFlags::empty(), + ); + } + + pub fn pop_stacking_context(&mut self) { + self.rf_mapper.pop_offset(); + self.push_item(&di::DisplayItem::PopStackingContext); + } + + pub fn push_stops(&mut self, stops: &[di::GradientStop]) { + if stops.is_empty() { + return; + } + self.push_item(&di::DisplayItem::SetGradientStops); + self.push_iter(stops); + } + + pub fn push_backdrop_filter( + &mut self, + common: &di::CommonItemProperties, + filters: &[di::FilterOp], + filter_datas: &[di::FilterData], + filter_primitives: &[di::FilterPrimitive], + ) { + self.push_filters(filters, filter_datas, filter_primitives); + + let item = di::DisplayItem::BackdropFilter(di::BackdropFilterDisplayItem { + common: *common, + }); + self.push_item(&item); + } + + pub fn push_filters( + &mut self, + filters: &[di::FilterOp], + filter_datas: &[di::FilterData], + filter_primitives: &[di::FilterPrimitive], + ) { + if !filters.is_empty() { + self.push_item(&di::DisplayItem::SetFilterOps); + self.push_iter(filters); + } + + for filter_data in filter_datas { + let func_types = [ + filter_data.func_r_type, filter_data.func_g_type, + filter_data.func_b_type, filter_data.func_a_type]; + self.push_item(&di::DisplayItem::SetFilterData); + self.push_iter(&func_types); + self.push_iter(&filter_data.r_values); + self.push_iter(&filter_data.g_values); + self.push_iter(&filter_data.b_values); + self.push_iter(&filter_data.a_values); + } + + if !filter_primitives.is_empty() { + self.push_item(&di::DisplayItem::SetFilterPrimitives); + self.push_iter(filter_primitives); + } + } + + fn generate_clip_index(&mut self) -> di::ClipId { + self.next_clip_index += 1; + di::ClipId(self.next_clip_index - 1, self.pipeline_id) + } + + fn generate_spatial_index(&mut self) -> di::SpatialId { + self.next_spatial_index += 1; + di::SpatialId::new(self.next_spatial_index - 1, self.pipeline_id) + } + + fn generate_clip_chain_id(&mut self) -> di::ClipChainId { + self.next_clip_chain_id += 1; + di::ClipChainId(self.next_clip_chain_id - 1, self.pipeline_id) + } + + pub fn define_scroll_frame( + &mut self, + parent_space: di::SpatialId, + external_id: di::ExternalScrollId, + content_rect: LayoutRect, + frame_rect: LayoutRect, + external_scroll_offset: LayoutVector2D, + scroll_offset_generation: APZScrollGeneration, + has_scroll_linked_effect: HasScrollLinkedEffect, + key: di::SpatialTreeItemKey, + ) -> di::SpatialId { + let scroll_frame_id = self.generate_spatial_index(); + let current_offset = self.current_offset(parent_space); + + let parent = self.spatial_nodes[parent_space.0].clone(); + + self.add_spatial_node_info( + scroll_frame_id, + parent.accumulated_external_scroll_offset + external_scroll_offset, + ); + + let descriptor = di::SpatialTreeItem::ScrollFrame(di::ScrollFrameDescriptor { + content_rect, + frame_rect: frame_rect.translate(current_offset), + parent_space, + scroll_frame_id, + external_id, + external_scroll_offset, + scroll_offset_generation, + has_scroll_linked_effect, + key, + }); + + self.push_spatial_tree_item(&descriptor); + + scroll_frame_id + } + + pub fn define_clip_chain<I>( + &mut self, + parent: Option<di::ClipChainId>, + clips: I, + ) -> di::ClipChainId + where + I: IntoIterator<Item = di::ClipId>, + I::IntoIter: ExactSizeIterator + Clone, + { + let id = self.generate_clip_chain_id(); + self.push_item(&di::DisplayItem::ClipChain(di::ClipChainItem { id, parent })); + self.push_iter(clips); + id + } + + pub fn define_clip_image_mask( + &mut self, + spatial_id: di::SpatialId, + image_mask: di::ImageMask, + points: &[LayoutPoint], + fill_rule: di::FillRule, + ) -> di::ClipId { + let id = self.generate_clip_index(); + + let current_offset = self.current_offset(spatial_id); + + let image_mask = di::ImageMask { + rect: image_mask.rect.translate(current_offset), + ..image_mask + }; + + 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); + self.push_iter(points); + } + self.push_item(&item); + id + } + + pub fn define_clip_rect( + &mut self, + spatial_id: di::SpatialId, + clip_rect: LayoutRect, + ) -> di::ClipId { + let id = self.generate_clip_index(); + + let current_offset = self.current_offset(spatial_id); + let clip_rect = clip_rect.translate(current_offset); + + let item = di::DisplayItem::RectClip(di::RectClipDisplayItem { + id, + spatial_id, + clip_rect, + }); + + self.push_item(&item); + id + } + + pub fn define_clip_rounded_rect( + &mut self, + spatial_id: di::SpatialId, + clip: di::ComplexClipRegion, + ) -> di::ClipId { + let id = self.generate_clip_index(); + + let current_offset = self.current_offset(spatial_id); + + let clip = di::ComplexClipRegion { + rect: clip.rect.translate(current_offset), + ..clip + }; + + let item = di::DisplayItem::RoundedRectClip(di::RoundedRectClipDisplayItem { + id, + spatial_id, + clip, + }); + + self.push_item(&item); + id + } + + pub fn define_sticky_frame( + &mut self, + 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, + key: di::SpatialTreeItemKey, + ) -> di::SpatialId { + let id = self.generate_spatial_index(); + let current_offset = self.current_offset(parent_spatial_id); + let parent = self.spatial_nodes[parent_spatial_id.0].clone(); + + self.add_spatial_node_info( + id, + parent.accumulated_external_scroll_offset, + ); + + let descriptor = di::SpatialTreeItem::StickyFrame(di::StickyFrameDescriptor { + parent_spatial_id, + id, + bounds: frame_rect.translate(current_offset), + margins, + vertical_offset_bounds, + horizontal_offset_bounds, + previously_applied_offset, + key, + }); + + self.push_spatial_tree_item(&descriptor); + id + } + + pub fn push_iframe( + &mut self, + bounds: LayoutRect, + clip_rect: LayoutRect, + space_and_clip: &di::SpaceAndClipInfo, + pipeline_id: PipelineId, + ignore_missing_pipeline: bool + ) { + let current_offset = self.current_offset(space_and_clip.spatial_id); + let bounds = bounds.translate(current_offset); + let clip_rect = clip_rect.translate(current_offset); + + let item = di::DisplayItem::Iframe(di::IframeDisplayItem { + bounds, + clip_rect, + space_and_clip: *space_and_clip, + pipeline_id, + ignore_missing_pipeline, + }); + self.push_item(&item); + } + + pub fn push_shadow( + &mut self, + space_and_clip: &di::SpaceAndClipInfo, + shadow: di::Shadow, + should_inflate: bool, + ) { + let item = di::DisplayItem::PushShadow(di::PushShadowDisplayItem { + space_and_clip: *space_and_clip, + shadow, + should_inflate, + }); + self.push_item(&item); + } + + pub fn pop_all_shadows(&mut self) { + self.push_item(&di::DisplayItem::PopAllShadows); + } + + pub fn start_item_group(&mut self) { + debug_assert!(!self.writing_to_chunk); + debug_assert!(self.pending_chunk.is_empty()); + + self.writing_to_chunk = true; + } + + fn flush_pending_item_group(&mut self, key: di::ItemKey) { + // Push RetainedItems-marker to cache_data section. + self.push_retained_items(key); + + // Push pending chunk to cache_data section. + self.payload.cache_data.append(&mut self.pending_chunk); + + // Push ReuseItems-marker to data section. + self.push_reuse_items(key); + } + + pub fn finish_item_group(&mut self, key: di::ItemKey) -> bool { + debug_assert!(self.writing_to_chunk); + self.writing_to_chunk = false; + + if self.pending_chunk.is_empty() { + return false; + } + + self.flush_pending_item_group(key); + true + } + + pub fn cancel_item_group(&mut self, discard: bool) { + debug_assert!(self.writing_to_chunk); + self.writing_to_chunk = false; + + if discard { + self.pending_chunk.clear(); + } else { + // Push pending chunk to data section. + self.payload.items_data.append(&mut self.pending_chunk); + } + } + + pub fn push_reuse_items(&mut self, key: di::ItemKey) { + self.push_item_to_section( + &di::DisplayItem::ReuseItems(key), + DisplayListSection::Data + ); + } + + fn push_retained_items(&mut self, key: di::ItemKey) { + self.push_item_to_section( + &di::DisplayItem::RetainedItems(key), + DisplayListSection::CacheData + ); + } + + pub fn set_cache_size(&mut self, cache_size: usize) { + self.cache_size = cache_size; + } + + pub fn begin(&mut self) { + assert_eq!(self.state, BuildState::Idle); + self.state = BuildState::Build; + self.builder_start_time = precise_time_ns(); + self.reset(); + } + + pub fn end(&mut self) -> (PipelineId, BuiltDisplayList) { + assert_eq!(self.state, BuildState::Build); + assert!(self.save_state.is_none(), "Finalized DisplayListBuilder with a pending save"); + + if let 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>(&mut self.payload.items_data); + ensure_red_zone::<di::DisplayItem>(&mut self.payload.cache_data); + ensure_red_zone::<di::SpatialTreeItem>(&mut self.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 { + cache_size: self.payload.cache_data.len(), + items_size: self.payload.items_data.len(), + spatial_tree_size: self.payload.spatial_tree.len(), + }; + let payload = mem::replace( + &mut self.payload, + DisplayListPayload::new(next_capacity), + ); + let end_time = precise_time_ns(); + + self.state = BuildState::Idle; + + ( + self.pipeline_id, + BuiltDisplayList { + descriptor: BuiltDisplayListDescriptor { + gecko_display_list_type: GeckoDisplayListType::None, + builder_start_time: self.builder_start_time, + builder_finish_time: end_time, + send_start_time: end_time, + total_clip_nodes: self.next_clip_index, + total_spatial_nodes: self.next_spatial_index, + cache_size: self.cache_size, + }, + payload, + }, + ) + } + + /// Retrieve the current offset to allow converting a stacking context + /// relative coordinate to be relative to the owing reference frame, + /// also considering any external scroll offset on the provided + /// spatial node. + fn current_offset( + &mut self, + spatial_id: di::SpatialId, + ) -> LayoutVector2D { + // Get the current offset from stacking context <-> reference frame space. + let rf_offset = self.rf_mapper.current_offset(); + + // Get the external scroll offset, if applicable. + let scroll_offset = self.spatial_nodes[spatial_id.0].accumulated_external_scroll_offset; + + rf_offset + scroll_offset + } + + /// Add info about a spatial node that is needed during DL building. + fn add_spatial_node_info( + &mut self, + id: di::SpatialId, + accumulated_external_scroll_offset: LayoutVector2D, + ) { + self.spatial_nodes.resize(id.0 + 1, SpatialNodeInfo::identity()); + + let info = &mut self.spatial_nodes[id.0]; + info.accumulated_external_scroll_offset = accumulated_external_scroll_offset; + } +} + +fn iter_spatial_tree<F>(spatial_tree: &[u8], mut f: F) where F: FnMut(&di::SpatialTreeItem) { + let mut src = spatial_tree; + let mut item = di::SpatialTreeItem::Invalid; + + while src.len() > di::SpatialTreeItem::max_size() { + src = peek_from_slice(src, &mut item); + f(&item); + } +} + +/// The offset stack for a given reference frame. +#[derive(Clone)] +struct ReferenceFrameState { + /// A stack of current offsets from the current reference frame scope. + offsets: Vec<LayoutVector2D>, +} + +/// Maps from stacking context layout coordinates into reference frame +/// relative coordinates. +#[derive(Clone)] +pub struct ReferenceFrameMapper { + /// A stack of reference frame scopes. + frames: Vec<ReferenceFrameState>, +} + +impl ReferenceFrameMapper { + pub fn new() -> Self { + ReferenceFrameMapper { + frames: vec![ + ReferenceFrameState { + offsets: vec![ + LayoutVector2D::zero(), + ], + } + ], + } + } + + /// Push a new scope. This resets the current offset to zero, and is + /// used when a new reference frame or iframe is pushed. + pub fn push_scope(&mut self) { + self.frames.push(ReferenceFrameState { + offsets: vec![ + LayoutVector2D::zero(), + ], + }); + } + + /// Pop a reference frame scope off the stack. + pub fn pop_scope(&mut self) { + self.frames.pop().unwrap(); + } + + /// Push a new offset for the current scope. This is used when + /// a new stacking context is pushed. + pub fn push_offset(&mut self, offset: LayoutVector2D) { + let frame = self.frames.last_mut().unwrap(); + let current_offset = *frame.offsets.last().unwrap(); + frame.offsets.push(current_offset + offset); + } + + /// Pop a local stacking context offset from the current scope. + pub fn pop_offset(&mut self) { + let frame = self.frames.last_mut().unwrap(); + frame.offsets.pop().unwrap(); + } + + /// Retrieve the current offset to allow converting a stacking context + /// relative coordinate to be relative to the owing reference frame. + /// TODO(gw): We could perhaps have separate coordinate spaces for this, + /// however that's going to either mean a lot of changes to + /// public API code, or a lot of changes to internal code. + /// Before doing that, we should revisit how Gecko would + /// prefer to provide coordinates. + /// TODO(gw): For now, this includes only the reference frame relative + /// offset. Soon, we will expand this to include the initial + /// scroll offsets that are now available on scroll nodes. This + /// will allow normalizing the coordinates even between display + /// lists where APZ has scrolled the content. + pub fn current_offset(&self) -> LayoutVector2D { + *self.frames.last().unwrap().offsets.last().unwrap() + } +} |