use api::{BorderRadius, ClipId, ClipMode, ColorF, DebugFlags, PrimitiveFlags, QualitySettings, RasterSpace};
use api::units::*;
use crate::clip::{clamped_radius, ClipItemKeyKind, ClipNodeId, ClipTreeBuilder, intersect_rounded_rects};
use crate::frame_builder::FrameBuilderConfig;
use crate::internal_types::FastHashMap;
use crate::picture::{PrimitiveList, PictureCompositeMode, PictureInstance, Picture3DContext, PictureFlags};
use crate::tile_cache::{SliceId, TileCacheParams};
use crate::prim_store::{PrimitiveInstance, PrimitiveStore, PictureIndex};
use crate::scene_building::SliceFlags;
use crate::scene_builder_thread::Interners;
use crate::spatial_tree::{SpatialNodeIndex, SceneSpatialTree};
use crate::util::VecHelper;
use std::mem;
const MAX_CACHE_SLICES: usize = 16;
struct SliceDescriptor {
prim_list: PrimitiveList,
scroll_root: SpatialNodeIndex,
}
enum SliceKind {
Default {
secondary_slices: Vec<SliceDescriptor>,
},
Atomic {
prim_list: PrimitiveList,
},
}
impl SliceKind {
fn default() -> Self {
SliceKind::Default {
secondary_slices: Vec::new(),
}
}
}
struct PrimarySlice {
kind: SliceKind,
background_color: Option<ColorF>,
iframe_clip: Option<ClipId>,
slice_flags: SliceFlags,
}
impl PrimarySlice {
fn new(
slice_flags: SliceFlags,
iframe_clip: Option<ClipId>,
background_color: Option<ColorF>,
) -> Self {
PrimarySlice {
kind: SliceKind::default(),
background_color,
iframe_clip,
slice_flags,
}
}
fn has_too_many_slices(&self) -> bool {
match self.kind {
SliceKind::Atomic { .. } => false,
SliceKind::Default { ref secondary_slices } => secondary_slices.len() > MAX_CACHE_SLICES,
}
}
fn merge(&mut self) {
self.slice_flags |= SliceFlags::IS_ATOMIC;
let old = mem::replace(
&mut self.kind,
SliceKind::Default { secondary_slices: Vec::new() },
);
self.kind = match old {
SliceKind::Default { mut secondary_slices } => {
let mut prim_list = PrimitiveList::empty();
for descriptor in secondary_slices.drain(..) {
prim_list.merge(descriptor.prim_list);
}
SliceKind::Atomic {
prim_list,
}
}
atomic => atomic,
}
}
}
pub struct TileCacheBuilder {
primary_slices: Vec<PrimarySlice>,
prev_scroll_root_cache: (SpatialNodeIndex, SpatialNodeIndex),
root_spatial_node_index: SpatialNodeIndex,
debug_flags: DebugFlags,
}
pub struct TileCacheConfig {
pub tile_caches: FastHashMap<SliceId, TileCacheParams>,
pub picture_cache_slice_count: usize,
}
impl TileCacheConfig {
pub fn new(picture_cache_slice_count: usize) -> Self {
TileCacheConfig {
tile_caches: FastHashMap::default(),
picture_cache_slice_count,
}
}
}
impl TileCacheBuilder {
pub fn new(
root_spatial_node_index: SpatialNodeIndex,
background_color: Option<ColorF>,
debug_flags: DebugFlags,
) -> Self {
TileCacheBuilder {
primary_slices: vec![PrimarySlice::new(SliceFlags::empty(), None, background_color)],
prev_scroll_root_cache: (SpatialNodeIndex::INVALID, SpatialNodeIndex::INVALID),
root_spatial_node_index,
debug_flags,
}
}
pub fn make_current_slice_atomic(&mut self) {
self.primary_slices
.last_mut()
.unwrap()
.merge();
}
pub fn is_current_slice_empty(&self) -> bool {
match self.primary_slices.last() {
Some(slice) => {
match slice.kind {
SliceKind::Default { ref secondary_slices } => {
secondary_slices.is_empty()
}
SliceKind::Atomic { ref prim_list } => {
prim_list.is_empty()
}
}
}
None => {
true
}
}
}
pub fn add_tile_cache_barrier(
&mut self,
slice_flags: SliceFlags,
iframe_clip: Option<ClipId>,
) {
let new_slice = PrimarySlice::new(
slice_flags,
iframe_clip,
None,
);
self.primary_slices.push(new_slice);
}
fn build_tile_cache(
&mut self,
prim_list: PrimitiveList,
spatial_tree: &SceneSpatialTree,
) -> Option<SliceDescriptor> {
if prim_list.is_empty() {
return None;
}
let mut scroll_root_occurrences = FastHashMap::default();
for cluster in &prim_list.clusters {
if cluster.spatial_node_index == SpatialNodeIndex::UNKNOWN {
continue;
}
let scroll_root = find_scroll_root(
cluster.spatial_node_index,
&mut self.prev_scroll_root_cache,
spatial_tree,
true,
);
*scroll_root_occurrences.entry(scroll_root).or_insert(0) += 1;
}
let scroll_roots: Vec<SpatialNodeIndex> = scroll_root_occurrences
.keys()
.cloned()
.collect();
scroll_root_occurrences.retain(|parent_spatial_node_index, _| {
scroll_roots.iter().all(|child_spatial_node_index| {
parent_spatial_node_index == child_spatial_node_index ||
spatial_tree.is_ancestor(
*parent_spatial_node_index,
*child_spatial_node_index,
)
})
});
let scroll_root = scroll_root_occurrences
.iter()
.max_by_key(|entry | entry.1)
.map(|(spatial_node_index, _)| *spatial_node_index)
.unwrap_or(self.root_spatial_node_index);
Some(SliceDescriptor {
scroll_root,
prim_list,
})
}
pub fn add_prim(
&mut self,
prim_instance: PrimitiveInstance,
prim_rect: LayoutRect,
spatial_node_index: SpatialNodeIndex,
prim_flags: PrimitiveFlags,
spatial_tree: &SceneSpatialTree,
quality_settings: &QualitySettings,
prim_instances: &mut Vec<PrimitiveInstance>,
clip_tree_builder: &ClipTreeBuilder,
) {
let primary_slice = self.primary_slices.last_mut().unwrap();
match primary_slice.kind {
SliceKind::Atomic { ref mut prim_list } => {
prim_list.add_prim(
prim_instance,
prim_rect,
spatial_node_index,
prim_flags,
prim_instances,
clip_tree_builder,
);
}
SliceKind::Default { ref mut secondary_slices } => {
assert_ne!(spatial_node_index, SpatialNodeIndex::UNKNOWN);
let scroll_root = find_scroll_root(
spatial_node_index,
&mut self.prev_scroll_root_cache,
spatial_tree,
!quality_settings.force_subpixel_aa_where_possible,
);
let current_scroll_root = secondary_slices
.last()
.map(|p| p.scroll_root);
let mut want_new_tile_cache = secondary_slices.is_empty();
if let Some(current_scroll_root) = current_scroll_root {
want_new_tile_cache |= match (current_scroll_root, scroll_root) {
(_, _) if current_scroll_root == self.root_spatial_node_index && scroll_root == self.root_spatial_node_index => {
false
}
(_, _) if current_scroll_root == self.root_spatial_node_index => {
true
}
(_, _) if scroll_root == self.root_spatial_node_index => {
if quality_settings.force_subpixel_aa_where_possible {
false
} else {
let mut create_slice = true;
let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id);
let mut current_node_id = leaf.node_id;
while current_node_id != ClipNodeId::NONE {
let node = clip_tree_builder.get_node(current_node_id);
let spatial_root = find_scroll_root(
node.spatial_node_index,
&mut self.prev_scroll_root_cache,
spatial_tree,
true,
);
if spatial_root != self.root_spatial_node_index {
create_slice = false;
break;
}
current_node_id = node.parent;
}
create_slice
}
}
(curr_scroll_root, scroll_root) => {
curr_scroll_root != scroll_root
}
};
}
if want_new_tile_cache {
secondary_slices.push(SliceDescriptor {
prim_list: PrimitiveList::empty(),
scroll_root,
});
}
secondary_slices
.last_mut()
.unwrap()
.prim_list
.add_prim(
prim_instance,
prim_rect,
spatial_node_index,
prim_flags,
prim_instances,
clip_tree_builder,
);
}
}
}
pub fn build(
mut self,
config: &FrameBuilderConfig,
prim_store: &mut PrimitiveStore,
spatial_tree: &SceneSpatialTree,
prim_instances: &[PrimitiveInstance],
clip_tree_builder: &mut ClipTreeBuilder,
interners: &Interners,
) -> (TileCacheConfig, Vec<PictureIndex>) {
let mut result = TileCacheConfig::new(self.primary_slices.len());
let mut tile_cache_pictures = Vec::new();
let primary_slices = std::mem::replace(&mut self.primary_slices, Vec::new());
let visibility_node = spatial_tree.root_reference_frame_index();
for mut primary_slice in primary_slices {
if primary_slice.has_too_many_slices() {
primary_slice.merge();
}
match primary_slice.kind {
SliceKind::Atomic { prim_list } => {
if let Some(descriptor) = self.build_tile_cache(
prim_list,
spatial_tree,
) {
create_tile_cache(
self.debug_flags,
primary_slice.slice_flags,
descriptor.scroll_root,
visibility_node,
primary_slice.iframe_clip,
descriptor.prim_list,
primary_slice.background_color,
prim_store,
prim_instances,
config,
&mut result.tile_caches,
&mut tile_cache_pictures,
clip_tree_builder,
interners,
spatial_tree,
);
}
}
SliceKind::Default { secondary_slices } => {
for descriptor in secondary_slices {
create_tile_cache(
self.debug_flags,
primary_slice.slice_flags,
descriptor.scroll_root,
visibility_node,
primary_slice.iframe_clip,
descriptor.prim_list,
primary_slice.background_color,
prim_store,
prim_instances,
config,
&mut result.tile_caches,
&mut tile_cache_pictures,
clip_tree_builder,
interners,
spatial_tree,
);
}
}
}
}
(result, tile_cache_pictures)
}
}
fn find_scroll_root(
spatial_node_index: SpatialNodeIndex,
prev_scroll_root_cache: &mut (SpatialNodeIndex, SpatialNodeIndex),
spatial_tree: &SceneSpatialTree,
allow_sticky_frames: bool,
) -> SpatialNodeIndex {
if prev_scroll_root_cache.0 == spatial_node_index {
return prev_scroll_root_cache.1;
}
let scroll_root = spatial_tree.find_scroll_root(spatial_node_index, allow_sticky_frames);
*prev_scroll_root_cache = (spatial_node_index, scroll_root);
scroll_root
}
fn create_tile_cache(
debug_flags: DebugFlags,
slice_flags: SliceFlags,
scroll_root: SpatialNodeIndex,
visibility_node: SpatialNodeIndex,
iframe_clip: Option<ClipId>,
prim_list: PrimitiveList,
background_color: Option<ColorF>,
prim_store: &mut PrimitiveStore,
prim_instances: &[PrimitiveInstance],
frame_builder_config: &FrameBuilderConfig,
tile_caches: &mut FastHashMap<SliceId, TileCacheParams>,
tile_cache_pictures: &mut Vec<PictureIndex>,
clip_tree_builder: &mut ClipTreeBuilder,
interners: &Interners,
spatial_tree: &SceneSpatialTree,
) {
let mut additional_clips = Vec::new();
if let Some(clip_id) = iframe_clip {
additional_clips.push(clip_id);
}
let mut shared_clip_node_id = None;
for cluster in &prim_list.clusters {
for prim_instance in &prim_instances[cluster.prim_range()] {
let leaf = clip_tree_builder.get_leaf(prim_instance.clip_leaf_id);
shared_clip_node_id = match shared_clip_node_id {
Some(current) => {
Some(clip_tree_builder.find_lowest_common_ancestor(current, leaf.node_id))
}
None => {
Some(leaf.node_id)
}
}
}
}
let mut shared_clip_node_id = shared_clip_node_id.unwrap_or(ClipNodeId::NONE);
let mut current_node_id = shared_clip_node_id;
let mut rounded_rect_count = 0;
let mut accumulated_rounded_rect: Option<(LayoutRect, BorderRadius)> = None;
while current_node_id != ClipNodeId::NONE {
let node = clip_tree_builder.get_node(current_node_id);
let clip_node_data = &interners.clip[node.handle];
let is_rcs = spatial_tree.is_root_coord_system(node.spatial_node_index);
let node_valid = if is_rcs {
match clip_node_data.key.kind {
ClipItemKeyKind::ImageMask(..) |
ClipItemKeyKind::Rectangle(ClipMode::ClipOut) |
ClipItemKeyKind::RoundedRectangle(_, ClipMode::ClipOut) => {
false
}
ClipItemKeyKind::RoundedRectangle(radius, ClipMode::Clip) => {
let br = clamped_radius(&BorderRadius::from(radius), node.unsnapped_clip_rect.size());
if br.can_use_fast_path_in(&node.unsnapped_clip_rect) {
rounded_rect_count += 1;
if accumulated_rounded_rect.is_none() {
accumulated_rounded_rect = Some((node.unsnapped_clip_rect, br));
}
true
} else {
false
}
}
ClipItemKeyKind::Rectangle(ClipMode::Clip) => {
true
}
}
} else {
false
};
if node_valid {
if rounded_rect_count > 1 {
let can_combine = match (accumulated_rounded_rect, clip_node_data.key.kind) {
(
Some((acc_rect, acc_radius)),
ClipItemKeyKind::RoundedRectangle(radius, ClipMode::Clip),
) => {
let radius = clamped_radius(&BorderRadius::from(radius), node.unsnapped_clip_rect.size());
intersect_rounded_rects(
acc_rect, acc_radius,
node.unsnapped_clip_rect, radius,
)
}
_ => None,
};
if let Some((combined_rect, combined_radius)) = can_combine {
rounded_rect_count = 1;
accumulated_rounded_rect = Some((combined_rect, combined_radius));
} else {
shared_clip_node_id = current_node_id;
rounded_rect_count = 1;
if let ClipItemKeyKind::RoundedRectangle(radius, ClipMode::Clip) = clip_node_data.key.kind {
let radius = clamped_radius(&BorderRadius::from(radius), node.unsnapped_clip_rect.size());
accumulated_rounded_rect = Some((node.unsnapped_clip_rect, radius));
}
}
}
} else {
shared_clip_node_id = node.parent;
rounded_rect_count = 0;
accumulated_rounded_rect = None;
}
current_node_id = node.parent;
}
let shared_clip_leaf_id = Some(clip_tree_builder.build_for_tile_cache(
shared_clip_node_id,
&additional_clips,
));
let slice = tile_cache_pictures.len();
let background_color = if slice == 0 {
background_color
} else {
None
};
let slice_id = SliceId::new(slice);
tile_caches.insert(slice_id, TileCacheParams {
debug_flags,
slice,
slice_flags,
spatial_node_index: scroll_root,
visibility_node_index: visibility_node,
background_color,
shared_clip_node_id,
shared_clip_leaf_id,
virtual_surface_size: frame_builder_config.compositor_kind.get_virtual_surface_size(),
image_surface_count: prim_list.image_surface_count,
yuv_image_surface_count: prim_list.yuv_image_surface_count,
});
let pic_index = prim_store.pictures.alloc().init(PictureInstance::new_image(
Some(PictureCompositeMode::TileCache { slice_id }),
Picture3DContext::Out,
PrimitiveFlags::IS_BACKFACE_VISIBLE,
prim_list,
scroll_root,
RasterSpace::Screen,
PictureFlags::empty(),
None,
));
tile_cache_pictures.push(PictureIndex(pic_index));
}
#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct PictureCacheDebugInfo {
pub slices: FastHashMap<usize, SliceDebugInfo>,
}
impl PictureCacheDebugInfo {
pub fn new() -> Self {
PictureCacheDebugInfo {
slices: FastHashMap::default(),
}
}
pub fn slice(&self, slice: usize) -> &SliceDebugInfo {
&self.slices[&slice]
}
}
impl Default for PictureCacheDebugInfo {
fn default() -> PictureCacheDebugInfo {
PictureCacheDebugInfo::new()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct CompositorClipDebugInfo {
pub rect: DeviceRect,
pub radius: BorderRadius,
}
#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct SliceDebugInfo {
pub tiles: FastHashMap<TileOffset, TileDebugInfo>,
pub compositor_clip: Option<CompositorClipDebugInfo>,
}
impl SliceDebugInfo {
pub fn new() -> Self {
SliceDebugInfo {
tiles: FastHashMap::default(),
compositor_clip: None,
}
}
pub fn tile(&self, x: i32, y: i32) -> &TileDebugInfo {
&self.tiles[&TileOffset::new(x, y)]
}
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct DirtyTileDebugInfo {
pub local_valid_rect: PictureRect,
pub local_dirty_rect: PictureRect,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum TileDebugInfo {
Occluded,
Culled,
Valid,
Dirty(DirtyTileDebugInfo),
}
impl TileDebugInfo {
pub fn is_occluded(&self) -> bool {
match self {
TileDebugInfo::Occluded => true,
TileDebugInfo::Culled |
TileDebugInfo::Valid |
TileDebugInfo::Dirty(..) => false,
}
}
pub fn is_valid(&self) -> bool {
match self {
TileDebugInfo::Valid => true,
TileDebugInfo::Culled |
TileDebugInfo::Occluded |
TileDebugInfo::Dirty(..) => false,
}
}
pub fn is_culled(&self) -> bool {
match self {
TileDebugInfo::Culled => true,
TileDebugInfo::Valid |
TileDebugInfo::Occluded |
TileDebugInfo::Dirty(..) => false,
}
}
pub fn as_dirty(&self) -> &DirtyTileDebugInfo {
match self {
TileDebugInfo::Occluded |
TileDebugInfo::Culled |
TileDebugInfo::Valid => {
panic!("not a dirty tile!");
}
TileDebugInfo::Dirty(ref info) => {
info
}
}
}
}