Bug 1580711 - Refactor storage of primitive instances into primitive clusters. r=nical

Previously, primitive lists were stored as:

  PicturePrimitive
    PrimitiveList
      [PrimitiveInstance]
      [PrimitiveCluster]
      [PictureIndices]

Each primitive instance contained a spatial node index and an
index into the primitive cluster it belongs to.

Now, the instances in a primitive list are stored as:

  PicturePrimitive
    PrimitiveList
      [PrimitiveCluster]
        [PrimitiveInstance]

This provides a number of advantages:
 * Size of the PrimitiveInstance struct is smaller.
 * No need to maintain a separate PictureIndices list.
 * Easy and fast to skip the array, finding pictures or scroll root changes.
 * Much faster to split and reorder PrimitiveList structures.

This patch is refactoring only, it doesn't contain any functional
changes. As we enable multiple picture caching slices, we need to
be able to split and reorder PrimitiveLists. Storing the primitive
instances in this way makes that process much more efficient than
it currently is.

Differential Revision: https://phabricator.services.mozilla.com/D45636

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Glenn Watson 2019-09-15 22:35:54 +00:00
parent 6c8fef0765
commit 804a9e983e
5 changed files with 953 additions and 803 deletions

View File

@ -645,20 +645,23 @@ impl BatchBuilder {
surface_spatial_node_index: SpatialNodeIndex,
z_generator: &mut ZBufferIdGenerator,
) {
// Add each run in this picture to the batch.
for prim_instance in &pic.prim_list.prim_instances {
self.add_prim_to_batch(
prim_instance,
ctx,
gpu_cache,
render_tasks,
deferred_resolves,
prim_headers,
transforms,
root_spatial_node_index,
surface_spatial_node_index,
z_generator,
);
for cluster in &pic.prim_list.clusters {
// Add each run in this picture to the batch.
for prim_instance in &cluster.prim_instances {
self.add_prim_to_batch(
prim_instance,
cluster.spatial_node_index,
ctx,
gpu_cache,
render_tasks,
deferred_resolves,
prim_headers,
transforms,
root_spatial_node_index,
surface_spatial_node_index,
z_generator,
);
}
}
}
@ -669,6 +672,7 @@ impl BatchBuilder {
fn add_prim_to_batch(
&mut self,
prim_instance: &PrimitiveInstance,
prim_spatial_node_index: SpatialNodeIndex,
ctx: &RenderTargetContext,
gpu_cache: &mut GpuCache,
render_tasks: &RenderTaskGraph,
@ -690,7 +694,7 @@ impl BatchBuilder {
let transform_id = transforms
.get_id(
prim_instance.spatial_node_index,
prim_spatial_node_index,
root_spatial_node_index,
ctx.clip_scroll_tree,
);
@ -1081,7 +1085,8 @@ impl BatchBuilder {
// Convert all children of the 3D hierarchy root into batches.
Picture3DContext::In { root_data: Some(ref list), .. } => {
for child in list {
let child_prim_instance = &picture.prim_list.prim_instances[child.anchor.prim_instance_index];
let cluster = &picture.prim_list.clusters[child.anchor.cluster_index];
let child_prim_instance = &cluster.prim_instances[child.anchor.instance_index];
let child_prim_info = &ctx.scratch.prim_info[child_prim_instance.visibility_info.0 as usize];
let child_pic_index = match child_prim_instance.kind {

View File

@ -417,7 +417,7 @@ impl<'a> DisplayListFlattener<'a> {
fn setup_picture_caching(
&mut self,
primitives: &mut Vec<PrimitiveInstance>,
main_prim_list: &mut PrimitiveList,
) {
if !self.config.enable_picture_caching {
return;
@ -523,105 +523,107 @@ impl<'a> DisplayListFlattener<'a> {
}
}
for (i, instance) in primitives.iter().enumerate() {
// If we encounter a push/pop clip, record where they occurred in the
// primitive list for later processing.
match instance.kind {
PrimitiveInstanceKind::PushClipChain => {
clip_chain_instance_stack.push(clip_chain_instances.len());
clip_chain_instances.push(ClipChainPairInfo {
push_index: i,
pop_index: usize::MAX,
spatial_node_index: instance.spatial_node_index,
clip_chain_id: instance.clip_chain_id,
});
// Invalidate the prim_clips cache - there is a new clip chain.
update_shared_clips = true;
continue;
}
PrimitiveInstanceKind::PopClipChain => {
let index = clip_chain_instance_stack.pop().unwrap();
let clip_chain_instance = &mut clip_chain_instances[index];
debug_assert_eq!(clip_chain_instance.pop_index, usize::MAX);
debug_assert_eq!(
clip_chain_instance.clip_chain_id,
instance.clip_chain_id,
);
debug_assert_eq!(
clip_chain_instance.spatial_node_index,
instance.spatial_node_index,
);
clip_chain_instance.pop_index = i;
// Invalidate the prim_clips cache - a clip chain was removed.
update_shared_clips = true;
continue;
}
_ => {}
}
for (cluster_index, cluster) in main_prim_list.clusters.iter().enumerate() {
let scroll_root = self.clip_scroll_tree.find_scroll_root(
cluster.spatial_node_index,
);
// If the primitive clip chain is different, then we need to rebuild prim_clips.
update_shared_clips |= last_prim_clip_chain_id != instance.clip_chain_id;
last_prim_clip_chain_id = instance.clip_chain_id;
for instance in &cluster.prim_instances {
// If we encounter a push/pop clip, record where they occurred in the
// primitive list for later processing.
match instance.kind {
PrimitiveInstanceKind::PushClipChain => {
clip_chain_instance_stack.push(clip_chain_instances.len());
clip_chain_instances.push(ClipChainPairInfo {
push_index: cluster_index,
pop_index: usize::MAX,
spatial_node_index: cluster.spatial_node_index,
clip_chain_id: instance.clip_chain_id,
});
// Invalidate the prim_clips cache - there is a new clip chain.
update_shared_clips = true;
continue;
}
PrimitiveInstanceKind::PopClipChain => {
let index = clip_chain_instance_stack.pop().unwrap();
let clip_chain_instance = &mut clip_chain_instances[index];
debug_assert_eq!(clip_chain_instance.pop_index, usize::MAX);
debug_assert_eq!(
clip_chain_instance.clip_chain_id,
instance.clip_chain_id,
);
debug_assert_eq!(
clip_chain_instance.spatial_node_index,
cluster.spatial_node_index,
);
clip_chain_instance.pop_index = cluster_index;
// Invalidate the prim_clips cache - a clip chain was removed.
update_shared_clips = true;
continue;
}
_ => {}
}
if update_shared_clips {
prim_clips.clear();
// Collect any clips from the clip chain stack that will affect this prim.
for clip_instance_index in &clip_chain_instance_stack {
let clip_instance = &clip_chain_instances[*clip_instance_index];
// If the primitive clip chain is different, then we need to rebuild prim_clips.
update_shared_clips |= last_prim_clip_chain_id != instance.clip_chain_id;
last_prim_clip_chain_id = instance.clip_chain_id;
if update_shared_clips {
prim_clips.clear();
// Collect any clips from the clip chain stack that will affect this prim.
for clip_instance_index in &clip_chain_instance_stack {
let clip_instance = &clip_chain_instances[*clip_instance_index];
add_clips(
clip_instance.clip_chain_id,
&mut prim_clips,
&self.clip_store,
);
}
// Collect any clips from the primitive's specific clip chain.
add_clips(
clip_instance.clip_chain_id,
instance.clip_chain_id,
&mut prim_clips,
&self.clip_store,
);
// We want to only retain clips that are shared across all primitives.
// TODO(gw): We could consider using a HashSet here, but:
// (a) The sizes of these arrays are typically very small (<< 10).
// (b) We would have to impl Ord/Eq on interner handles, which we
// otherwise don't need / want.
shared_clips.retain(|h1: &ClipDataHandle| {
let uid = h1.uid();
prim_clips.iter().any(|h2| {
uid == h2.uid()
})
});
update_shared_clips = false;
}
// Collect any clips from the primitive's specific clip chain.
add_clips(
instance.clip_chain_id,
&mut prim_clips,
&self.clip_store,
);
// We want to only retain clips that are shared across all primitives.
// TODO(gw): We could consider using a HashSet here, but:
// (a) The sizes of these arrays are typically very small (<< 10).
// (b) We would have to impl Ord/Eq on interner handles, which we
// otherwise don't need / want.
shared_clips.retain(|h1: &ClipDataHandle| {
let uid = h1.uid();
prim_clips.iter().any(|h2| {
uid == h2.uid()
})
});
update_shared_clips = false;
}
let scroll_root = self.clip_scroll_tree.find_scroll_root(
instance.spatial_node_index,
);
if scroll_root != ROOT_SPATIAL_NODE_INDEX {
// If we find multiple scroll roots in this page, then skip
// picture caching for now. In future, we can handle picture
// caching on these sites by creating a tile cache per
// scroll root, or (more likely) selecting the common parent
// scroll root between the detected scroll roots.
match main_scroll_root {
Some(main_scroll_root) => {
if main_scroll_root != scroll_root {
return;
if scroll_root != ROOT_SPATIAL_NODE_INDEX {
// If we find multiple scroll roots in this page, then skip
// picture caching for now. In future, we can handle picture
// caching on these sites by creating a tile cache per
// scroll root, or (more likely) selecting the common parent
// scroll root between the detected scroll roots.
match main_scroll_root {
Some(main_scroll_root) => {
if main_scroll_root != scroll_root {
return;
}
}
None => {
main_scroll_root = Some(scroll_root);
}
}
None => {
main_scroll_root = Some(scroll_root);
}
}
if first_index.is_none() {
// The first time we identify a prim that will be cached, set the prim_clips
// array to this, such that the retain() logic above works.
shared_clips = prim_clips.clone();
first_index = Some(i);
if first_index.is_none() {
// The first time we identify a prim that will be cached, set the prim_clips
// array to this, such that the retain() logic above works.
shared_clips = prim_clips.clone();
first_index = Some(cluster_index);
}
}
}
}
@ -632,7 +634,7 @@ impl<'a> DisplayListFlattener<'a> {
};
// Get the list of existing primitives in the main stacking context.
let mut old_prim_list = primitives.take();
let mut old_prim_list = mem::replace(main_prim_list, PrimitiveList::empty());
// In the simple case, there are no preceding or trailing primitives,
// because everything is anchored to the root scroll node. Handle
@ -649,7 +651,7 @@ impl<'a> DisplayListFlattener<'a> {
preceding_prims = old_prim_list;
}
None => {
preceding_prims = Vec::new();
preceding_prims = PrimitiveList::empty();
remaining_prims = old_prim_list;
}
}
@ -659,29 +661,29 @@ impl<'a> DisplayListFlattener<'a> {
// Step through each clip chain pair, and see if it crosses a slice boundary.
for clip_chain_instance in clip_chain_instances {
if clip_chain_instance.push_index < mid_index && clip_chain_instance.pop_index >= mid_index {
preceding_prims.push(
preceding_prims.add_prim(
create_clip_prim_instance(
clip_chain_instance.spatial_node_index,
clip_chain_instance.clip_chain_id,
PrimitiveInstanceKind::PopClipChain,
)
),
LayoutSize::zero(),
clip_chain_instance.spatial_node_index,
true,
);
remaining_prims.insert(
0,
remaining_prims.add_prim_to_start(
create_clip_prim_instance(
clip_chain_instance.spatial_node_index,
clip_chain_instance.clip_chain_id,
PrimitiveInstanceKind::PushClipChain,
)
),
LayoutSize::zero(),
clip_chain_instance.spatial_node_index,
true,
);
}
}
let prim_list = PrimitiveList::new(
remaining_prims,
&self.interners,
);
let prim_list = remaining_prims;
// Now, create a picture with tile caching enabled that will hold all
// of the primitives selected as belonging to the main scroll root.
@ -747,14 +749,17 @@ impl<'a> DisplayListFlattener<'a> {
segment_instance_index: SegmentInstanceIndex::INVALID,
},
parent_clip_chain_id,
main_scroll_root,
);
// This contains the tile caching picture, with preceding and
// trailing primitives outside the main scroll root.
primitives.reserve(preceding_prims.len() + 1);
primitives.extend(preceding_prims);
primitives.push(instance);
main_prim_list.extend(preceding_prims);
main_prim_list.add_prim(
instance,
LayoutSize::zero(),
main_scroll_root,
true,
);
}
fn flatten_items(
@ -1605,7 +1610,6 @@ impl<'a> DisplayListFlattener<'a> {
info.clip_rect,
instance_kind,
clip_chain_id,
spatial_node_index,
)
}
@ -1653,6 +1657,9 @@ impl<'a> DisplayListFlattener<'a> {
pub fn add_primitive_to_draw_list(
&mut self,
prim_instance: PrimitiveInstance,
prim_size: LayoutSize,
spatial_node_index: SpatialNodeIndex,
is_backface_visible: bool,
) {
// Add primitive to the top-most stacking context on the stack.
if prim_instance.is_chased() {
@ -1660,7 +1667,12 @@ impl<'a> DisplayListFlattener<'a> {
}
let stacking_context = self.sc_stack.last_mut().unwrap();
stacking_context.primitives.push(prim_instance);
stacking_context.prim_list.add_prim(
prim_instance,
prim_size,
spatial_node_index,
is_backface_visible,
);
}
/// Convenience interface that creates a primitive entry and adds it
@ -1746,7 +1758,12 @@ impl<'a> DisplayListFlattener<'a> {
&prim_instance,
);
self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
self.add_primitive_to_draw_list(prim_instance);
self.add_primitive_to_draw_list(
prim_instance,
info.rect.size,
clip_and_scroll.spatial_node_index,
info.is_backface_visible,
);
}
pub fn push_stacking_context(
@ -1800,7 +1817,14 @@ impl<'a> DisplayListFlattener<'a> {
Some(PictureCompositeMode::Blit(BlitReason::PRESERVE3D)),
flat_items_context_3d,
);
(true, extra_instance.map(|(_, instance)| instance))
let extra_instance = extra_instance.map(|(_, instance)| {
ExtendedPrimitiveInstance {
instance,
spatial_node_index: sc.spatial_node_index,
is_backface_visible: sc.is_backface_visible,
}
});
(true, extra_instance)
},
_ => (false, None),
};
@ -1874,7 +1898,7 @@ impl<'a> DisplayListFlattener<'a> {
// Push the SC onto the stack, so we know how to handle things in
// pop_stacking_context.
self.sc_stack.push(FlattenedStackingContext {
primitives: Vec::new(),
prim_list: PrimitiveList::empty(),
pipeline_id,
is_backface_visible,
requested_raster_space,
@ -1908,41 +1932,49 @@ impl<'a> DisplayListFlattener<'a> {
if stacking_context.is_redundant(parent_sc) {
if stacking_context.clip_chain_id != ClipChainId::NONE {
let prim = create_clip_prim_instance(
stacking_context.spatial_node_index,
stacking_context.clip_chain_id,
PrimitiveInstanceKind::PushClipChain,
);
parent_sc.primitives.push(prim);
parent_sc.prim_list.add_prim(
prim,
LayoutSize::zero(),
stacking_context.spatial_node_index,
true,
);
}
// If the parent context primitives list is empty, it's faster
// to assign the storage of the popped context instead of paying
// the copying cost for extend.
if parent_sc.primitives.is_empty() {
parent_sc.primitives = stacking_context.primitives;
if parent_sc.prim_list.is_empty() {
parent_sc.prim_list = stacking_context.prim_list;
} else {
parent_sc.primitives.extend(stacking_context.primitives);
parent_sc.prim_list.extend(stacking_context.prim_list);
}
if stacking_context.clip_chain_id != ClipChainId::NONE {
let prim = create_clip_prim_instance(
stacking_context.spatial_node_index,
stacking_context.clip_chain_id,
PrimitiveInstanceKind::PopClipChain,
);
parent_sc.primitives.push(prim);
parent_sc.prim_list.add_prim(
prim,
LayoutSize::zero(),
stacking_context.spatial_node_index,
true,
);
}
return;
}
parent_sc.primitives.is_empty()
parent_sc.prim_list.is_empty()
},
None => true,
};
if stacking_context.create_tile_cache {
self.setup_picture_caching(
&mut stacking_context.primitives,
&mut stacking_context.prim_list,
);
}
@ -1983,11 +2015,6 @@ impl<'a> DisplayListFlattener<'a> {
let scroll_root = ROOT_SPATIAL_NODE_INDEX;
let prim_list = PrimitiveList::new(
stacking_context.primitives,
&self.interners,
);
// Now, create a picture with tile caching enabled that will hold all
// of the primitives selected as belonging to the main scroll root.
let pic_key = PictureKey::new(
@ -2029,7 +2056,7 @@ impl<'a> DisplayListFlattener<'a> {
true,
true,
RasterSpace::Screen,
prim_list,
stacking_context.prim_list,
scroll_root,
Some(Box::new(tile_cache)),
PictureOptions::default(),
@ -2044,10 +2071,16 @@ impl<'a> DisplayListFlattener<'a> {
segment_instance_index: SegmentInstanceIndex::INVALID,
},
ClipChainId::NONE,
scroll_root,
);
stacking_context.primitives = vec![instance];
let mut prim_list = PrimitiveList::empty();
prim_list.add_prim(
instance,
LayoutSize::zero(),
scroll_root,
true,
);
stacking_context.prim_list = prim_list;
}
// Add picture for this actual stacking context contents to render into.
@ -2060,10 +2093,7 @@ impl<'a> DisplayListFlattener<'a> {
true,
stacking_context.is_backface_visible,
stacking_context.requested_raster_space,
PrimitiveList::new(
stacking_context.primitives,
&self.interners,
),
stacking_context.prim_list,
stacking_context.spatial_node_index,
None,
PictureOptions::default(),
@ -2079,7 +2109,6 @@ impl<'a> DisplayListFlattener<'a> {
leaf_composite_mode.into(),
stacking_context.is_backface_visible,
ClipChainId::NONE,
stacking_context.spatial_node_index,
&mut self.interners,
);
@ -2091,7 +2120,21 @@ impl<'a> DisplayListFlattener<'a> {
// a picture with all the *trailing* immediate children elements.
// We append this to the preserve-3D picture set and make a container picture of them.
if let Picture3DContext::In { root_data: Some(mut prims), ancestor_index } = stacking_context.context_3d {
prims.push(cur_instance);
prims.push(ExtendedPrimitiveInstance {
instance: cur_instance,
spatial_node_index: stacking_context.spatial_node_index,
is_backface_visible: stacking_context.is_backface_visible,
});
let mut prim_list = PrimitiveList::empty();
for ext_prim in prims.drain(..) {
prim_list.add_prim(
ext_prim.instance,
LayoutSize::zero(),
ext_prim.spatial_node_index,
ext_prim.is_backface_visible,
);
}
// This is the acttual picture representing our 3D hierarchy root.
current_pic_index = PictureIndex(self.prim_store.pictures
@ -2106,10 +2149,7 @@ impl<'a> DisplayListFlattener<'a> {
true,
stacking_context.is_backface_visible,
stacking_context.requested_raster_space,
PrimitiveList::new(
prims,
&self.interners,
),
prim_list,
stacking_context.spatial_node_index,
None,
PictureOptions::default(),
@ -2121,7 +2161,6 @@ impl<'a> DisplayListFlattener<'a> {
PictureCompositeKey::Identity,
stacking_context.is_backface_visible,
ClipChainId::NONE,
stacking_context.spatial_node_index,
&mut self.interners,
);
}
@ -2156,6 +2195,14 @@ impl<'a> DisplayListFlattener<'a> {
let has_mix_blend = if let (Some(mix_blend_mode), false) = (stacking_context.composite_ops.mix_blend_mode, parent_is_empty) {
let composite_mode = Some(PictureCompositeMode::MixBlend(mix_blend_mode));
let mut prim_list = PrimitiveList::empty();
prim_list.add_prim(
cur_instance.clone(),
LayoutSize::zero(),
stacking_context.spatial_node_index,
stacking_context.is_backface_visible,
);
let blend_pic_index = PictureIndex(self.prim_store.pictures
.alloc()
.init(PicturePrimitive::new_image(
@ -2165,10 +2212,7 @@ impl<'a> DisplayListFlattener<'a> {
true,
stacking_context.is_backface_visible,
stacking_context.requested_raster_space,
PrimitiveList::new(
vec![cur_instance.clone()],
&self.interners,
),
prim_list,
stacking_context.spatial_node_index,
None,
PictureOptions::default(),
@ -2181,7 +2225,6 @@ impl<'a> DisplayListFlattener<'a> {
composite_mode.into(),
stacking_context.is_backface_visible,
ClipChainId::NONE,
stacking_context.spatial_node_index,
&mut self.interners,
);
@ -2213,7 +2256,12 @@ impl<'a> DisplayListFlattener<'a> {
if has_mix_blend {
parent_sc.blit_reason |= BlitReason::ISOLATE;
}
parent_sc.primitives.push(cur_instance);
parent_sc.prim_list.add_prim(
cur_instance,
LayoutSize::zero(),
stacking_context.spatial_node_index,
stacking_context.is_backface_visible,
);
None
}
// This must be the root stacking context
@ -2226,7 +2274,11 @@ impl<'a> DisplayListFlattener<'a> {
// finally, if there any outstanding 3D primitive instances,
// find the 3D hierarchy root and add them there.
if let Some(instance) = trailing_children_instance {
self.add_primitive_instance_to_3d_root(instance);
self.add_primitive_instance_to_3d_root(ExtendedPrimitiveInstance {
instance,
spatial_node_index: stacking_context.spatial_node_index,
is_backface_visible: stacking_context.is_backface_visible,
});
}
assert!(
@ -2500,7 +2552,7 @@ impl<'a> DisplayListFlattener<'a> {
// Add any primitives that come after this shadow in the item
// list to this shadow.
let mut prims = Vec::new();
let mut prim_list = PrimitiveList::empty();
for item in &items {
match item {
@ -2508,35 +2560,35 @@ impl<'a> DisplayListFlattener<'a> {
self.add_shadow_prim(
&pending_shadow,
pending_image,
&mut prims,
&mut prim_list,
)
}
ShadowItem::LineDecoration(ref pending_line_dec) => {
self.add_shadow_prim(
&pending_shadow,
pending_line_dec,
&mut prims,
&mut prim_list,
)
}
ShadowItem::NormalBorder(ref pending_border) => {
self.add_shadow_prim(
&pending_shadow,
pending_border,
&mut prims,
&mut prim_list,
)
}
ShadowItem::Primitive(ref pending_primitive) => {
self.add_shadow_prim(
&pending_shadow,
pending_primitive,
&mut prims,
&mut prim_list,
)
}
ShadowItem::TextRun(ref pending_text_run) => {
self.add_shadow_prim(
&pending_shadow,
pending_text_run,
&mut prims,
&mut prim_list,
)
}
_ => {}
@ -2545,7 +2597,7 @@ impl<'a> DisplayListFlattener<'a> {
// No point in adding a shadow here if there were no primitives
// added to the shadow.
if !prims.is_empty() {
if !prim_list.is_empty() {
// Create a picture that the shadow primitives will be added to. If the
// blur radius is 0, the code in Picture::prepare_for_render will
// detect this and mark the picture to be drawn directly into the
@ -2572,10 +2624,7 @@ impl<'a> DisplayListFlattener<'a> {
is_passthrough,
is_backface_visible,
raster_space,
PrimitiveList::new(
prims,
&self.interners,
),
prim_list,
pending_shadow.clip_and_scroll.spatial_node_index,
None,
options,
@ -2607,12 +2656,16 @@ impl<'a> DisplayListFlattener<'a> {
segment_instance_index: SegmentInstanceIndex::INVALID,
},
pending_shadow.clip_and_scroll.clip_chain_id,
pending_shadow.clip_and_scroll.spatial_node_index,
);
// Add the shadow primitive. This must be done before pushing this
// picture on to the shadow stack, to avoid infinite recursion!
self.add_primitive_to_draw_list(shadow_prim_instance);
self.add_primitive_to_draw_list(
shadow_prim_instance,
LayoutSize::zero(),
pending_shadow.clip_and_scroll.spatial_node_index,
true,
);
}
}
ShadowItem::Image(pending_image) => {
@ -2651,7 +2704,7 @@ impl<'a> DisplayListFlattener<'a> {
&mut self,
pending_shadow: &PendingShadow,
pending_primitive: &PendingPrimitive<P>,
prims: &mut Vec<PrimitiveInstance>,
prim_list: &mut PrimitiveList,
)
where
P: InternablePrimitive + CreateShadow,
@ -2685,7 +2738,12 @@ impl<'a> DisplayListFlattener<'a> {
);
// Add the new primitive to the shadow picture.
prims.push(shadow_prim_instance);
prim_list.add_prim(
shadow_prim_instance,
info.rect.size,
pending_primitive.clip_and_scroll.spatial_node_index,
info.is_backface_visible,
);
}
fn add_shadow_prim_to_draw_list<P>(
@ -3185,12 +3243,15 @@ impl<'a> DisplayListFlattener<'a> {
);
}
pub fn add_primitive_instance_to_3d_root(&mut self, instance: PrimitiveInstance) {
fn add_primitive_instance_to_3d_root(
&mut self,
prim: ExtendedPrimitiveInstance,
) {
// find the 3D root and append to the children list
for sc in self.sc_stack.iter_mut().rev() {
match sc.context_3d {
Picture3DContext::In { root_data: Some(ref mut prims), .. } => {
prims.push(instance);
prims.push(prim);
break;
}
Picture3DContext::In { .. } => {}
@ -3240,6 +3301,14 @@ impl<'a> DisplayListFlattener<'a> {
let is_backface_visible = stacking_context.is_backface_visible;
let composite_mode = None;
let mut prim_list = PrimitiveList::empty();
prim_list.add_prim(
instance,
LayoutSize::zero(),
backdrop_spatial_node_index,
is_backface_visible,
);
backdrop_pic_index = PictureIndex(self.prim_store.pictures
.alloc()
.init(PicturePrimitive::new_image(
@ -3249,10 +3318,7 @@ impl<'a> DisplayListFlattener<'a> {
true,
is_backface_visible,
requested_raster_space,
PrimitiveList::new(
vec![instance],
&mut self.interners,
),
prim_list,
backdrop_spatial_node_index,
None,
PictureOptions {
@ -3266,7 +3332,6 @@ impl<'a> DisplayListFlattener<'a> {
composite_mode.into(),
is_backface_visible,
clip_chain_id,
backdrop_spatial_node_index,
&mut self.interners,
);
}
@ -3312,16 +3377,34 @@ impl<'a> DisplayListFlattener<'a> {
filtered_instance.clip_chain_id = clip_and_scroll.clip_chain_id;
self.sc_stack.iter_mut().rev().find(|sc| sc.is_backdrop_root).unwrap().primitives.push(filtered_instance);
self.sc_stack
.iter_mut()
.rev()
.find(|sc| sc.is_backdrop_root)
.unwrap()
.prim_list
.add_prim(
filtered_instance,
LayoutSize::zero(),
backdrop_spatial_node_index,
info.is_backface_visible,
);
}
pub fn cut_backdrop_picture(&mut self) -> Option<PictureIndex> {
let mut flattened_items = None;
let mut backdrop_root = None;
let mut spatial_node_index = SpatialNodeIndex::INVALID;
let mut is_backface_visible = true;
for sc in self.sc_stack.iter_mut().rev() {
// Add child contents to parent stacking context
if let Some((_, flattened_instance)) = flattened_items.take() {
sc.primitives.push(flattened_instance);
sc.prim_list.add_prim(
flattened_instance,
LayoutSize::zero(),
spatial_node_index,
is_backface_visible,
);
}
flattened_items = sc.cut_item_sequence(
&mut self.prim_store,
@ -3329,6 +3412,8 @@ impl<'a> DisplayListFlattener<'a> {
None,
Picture3DContext::Out,
);
spatial_node_index = sc.spatial_node_index;
is_backface_visible = sc.is_backface_visible;
if sc.is_backdrop_root {
backdrop_root = Some(sc);
break;
@ -3337,7 +3422,14 @@ impl<'a> DisplayListFlattener<'a> {
let (pic_index, instance) = flattened_items?;
self.prim_store.pictures[pic_index.0].requested_composite_mode = Some(PictureCompositeMode::Blit(BlitReason::BACKDROP));
backdrop_root.expect("no backdrop root found").primitives.push(instance);
backdrop_root.expect("no backdrop root found")
.prim_list
.add_prim(
instance,
LayoutSize::zero(),
spatial_node_index,
is_backface_visible,
);
Some(pic_index)
}
@ -3397,6 +3489,14 @@ impl<'a> DisplayListFlattener<'a> {
_ => PictureCompositeMode::Filter(filter.clone()),
});
let mut prim_list = PrimitiveList::empty();
prim_list.add_prim(
cur_instance.clone(),
LayoutSize::zero(),
spatial_node_index,
is_backface_visible,
);
let filter_pic_index = PictureIndex(self.prim_store.pictures
.alloc()
.init(PicturePrimitive::new_image(
@ -3406,10 +3506,7 @@ impl<'a> DisplayListFlattener<'a> {
true,
is_backface_visible,
requested_raster_space,
PrimitiveList::new(
vec![cur_instance.clone()],
&mut self.interners,
),
prim_list,
spatial_node_index,
None,
PictureOptions {
@ -3424,7 +3521,6 @@ impl<'a> DisplayListFlattener<'a> {
composite_mode.into(),
is_backface_visible,
ClipChainId::NONE,
spatial_node_index,
&mut self.interners,
);
@ -3464,6 +3560,14 @@ impl<'a> DisplayListFlattener<'a> {
filter_datas,
);
let mut prim_list = PrimitiveList::empty();
prim_list.add_prim(
cur_instance.clone(),
LayoutSize::zero(),
spatial_node_index,
is_backface_visible,
);
let filter_pic_index = PictureIndex(self.prim_store.pictures
.alloc()
.init(PicturePrimitive::new_image(
@ -3473,10 +3577,7 @@ impl<'a> DisplayListFlattener<'a> {
true,
is_backface_visible,
requested_raster_space,
PrimitiveList::new(
vec![cur_instance.clone()],
&mut self.interners,
),
prim_list,
spatial_node_index,
None,
PictureOptions {
@ -3491,7 +3592,6 @@ impl<'a> DisplayListFlattener<'a> {
Some(composite_mode).into(),
is_backface_visible,
ClipChainId::NONE,
spatial_node_index,
&mut self.interners,
);
@ -3516,12 +3616,21 @@ pub trait IsVisible {
fn is_visible(&self) -> bool;
}
/// A primitive instance + some extra information about the primitive. This is
/// stored when constructing 3d rendering contexts, which involve cutting
/// primitive lists.
struct ExtendedPrimitiveInstance {
instance: PrimitiveInstance,
spatial_node_index: SpatialNodeIndex,
is_backface_visible: bool,
}
/// Properties of a stacking context that are maintained
/// during creation of the scene. These structures are
/// not persisted after the initial scene build.
struct FlattenedStackingContext {
/// The list of primitive instances added to this stacking context.
primitives: Vec<PrimitiveInstance>,
prim_list: PrimitiveList,
/// Whether this stacking context is visible when backfacing
is_backface_visible: bool,
@ -3555,7 +3664,7 @@ struct FlattenedStackingContext {
transform_style: TransformStyle,
/// Defines the relationship to a preserve-3D hiearachy.
context_3d: Picture3DContext<PrimitiveInstance>,
context_3d: Picture3DContext<ExtendedPrimitiveInstance>,
/// If true, create a tile cache for this stacking context.
create_tile_cache: bool,
@ -3600,7 +3709,7 @@ impl FlattenedStackingContext {
// We can skip mix-blend modes if they are the first primitive in a stacking context,
// see pop_stacking_context for a full explanation.
if !self.composite_ops.mix_blend_mode.is_none() &&
!parent.primitives.is_empty() {
!parent.prim_list.is_empty() {
return false;
}
@ -3636,7 +3745,7 @@ impl FlattenedStackingContext {
composite_mode: Option<PictureCompositeMode>,
flat_items_context_3d: Picture3DContext<OrderedPictureChild>,
) -> Option<(PictureIndex, PrimitiveInstance)> {
if self.primitives.is_empty() {
if self.prim_list.is_empty() {
return None
}
@ -3649,10 +3758,7 @@ impl FlattenedStackingContext {
true,
self.is_backface_visible,
self.requested_raster_space,
PrimitiveList::new(
mem::replace(&mut self.primitives, Vec::new()),
interners,
),
mem::replace(&mut self.prim_list, PrimitiveList::empty()),
self.spatial_node_index,
None,
PictureOptions::default(),
@ -3664,7 +3770,6 @@ impl FlattenedStackingContext {
composite_mode.into(),
self.is_backface_visible,
self.clip_chain_id,
self.spatial_node_index,
interners,
);
@ -3733,7 +3838,6 @@ fn create_prim_instance(
composite_mode_key: PictureCompositeKey,
is_backface_visible: bool,
clip_chain_id: ClipChainId,
spatial_node_index: SpatialNodeIndex,
interners: &mut Interners,
) -> PrimitiveInstance {
let pic_key = PictureKey::new(
@ -3761,12 +3865,10 @@ fn create_prim_instance(
segment_instance_index: SegmentInstanceIndex::INVALID,
},
clip_chain_id,
spatial_node_index,
)
}
fn create_clip_prim_instance(
spatial_node_index: SpatialNodeIndex,
clip_chain_id: ClipChainId,
kind: PrimitiveInstanceKind,
) -> PrimitiveInstance {
@ -3775,7 +3877,6 @@ fn create_clip_prim_instance(
LayoutRect::max_rect(),
kind,
clip_chain_id,
spatial_node_index,
)
}

View File

@ -33,16 +33,24 @@ pub type FastHashSet<K> = HashSet<K, BuildHasherDefault<FxHasher>>;
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct PlaneSplitAnchor {
// TODO(gw): For now, this is a simple index into the owning picture's
// prim_instances array. In future, it will identify both
// a cluster and primitive index within that cluster.
pub prim_instance_index: usize,
pub cluster_index: usize,
pub instance_index: usize,
}
impl PlaneSplitAnchor {
pub fn new(cluster_index: usize, instance_index: usize) -> Self {
PlaneSplitAnchor {
cluster_index,
instance_index,
}
}
}
impl Default for PlaneSplitAnchor {
fn default() -> Self {
PlaneSplitAnchor {
prim_instance_index: 0,
cluster_index: 0,
instance_index: 0,
}
}
}

View File

@ -33,9 +33,8 @@ use crate::render_target::RenderTargetKind;
use crate::render_task::{RenderTask, RenderTaskLocation, BlurTaskCache, ClearMode};
use crate::resource_cache::ResourceCache;
use crate::scene::SceneProperties;
use crate::scene_builder::Interners;
use smallvec::SmallVec;
use std::{mem, u8, u16};
use std::{mem, u8};
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::texture_cache::TextureCacheHandle;
use crate::util::{TransformedRectKind, MatrixHelpers, MaxRect, scale_factors, VecHelper};
@ -1447,6 +1446,7 @@ impl TileCacheInstance {
pub fn update_prim_dependencies(
&mut self,
prim_instance: &PrimitiveInstance,
prim_spatial_node_index: SpatialNodeIndex,
prim_clip_chain: Option<&ClipChainInstance>,
local_prim_rect: LayoutRect,
clip_scroll_tree: &ClipScrollTree,
@ -1459,7 +1459,7 @@ impl TileCacheInstance {
surface_index: SurfaceIndex,
) -> bool {
self.map_local_to_surface.set_target_spatial_node(
prim_instance.spatial_node_index,
prim_spatial_node_index,
clip_scroll_tree,
);
@ -1497,8 +1497,8 @@ impl TileCacheInstance {
);
// Include the prim spatial node, if differs relative to cache root.
if prim_instance.spatial_node_index != self.spatial_node_index {
prim_info.spatial_nodes.push(prim_instance.spatial_node_index);
if prim_spatial_node_index != self.spatial_node_index {
prim_info.spatial_nodes.push(prim_spatial_node_index);
}
// If there was a clip chain, add any clip dependencies to the list for this tile.
@ -1563,7 +1563,7 @@ impl TileCacheInstance {
let same_coord_system = {
let prim_spatial_node = &clip_scroll_tree
.spatial_nodes[prim_instance.spatial_node_index.0 as usize];
.spatial_nodes[prim_spatial_node_index.0 as usize];
let surface_spatial_node = &clip_scroll_tree
.spatial_nodes[self.spatial_node_index.0 as usize];
@ -1867,15 +1867,24 @@ impl<'a> PictureUpdateState<'a> {
self,
frame_context,
) {
for child_pic_index in &prim_list.pictures {
self.update(
*child_pic_index,
picture_primitives,
frame_context,
gpu_cache,
clip_store,
data_stores,
);
for cluster in &prim_list.clusters {
if cluster.flags.contains(ClusterFlags::IS_PICTURE) {
for prim_instance in &cluster.prim_instances {
let child_pic_index = match prim_instance.kind {
PrimitiveInstanceKind::Picture { pic_index, .. } => pic_index,
_ => unreachable!(),
};
self.update(
child_pic_index,
picture_primitives,
frame_context,
gpu_cache,
clip_store,
data_stores,
);
}
}
}
picture_primitives[pic_index.0].post_update(
@ -1912,8 +1921,20 @@ impl<'a> PictureUpdateState<'a> {
None => fallback_raster_spatial_node,
};
for child_pic_index in &picture.prim_list.pictures {
self.assign_raster_roots(*child_pic_index, picture_primitives, new_fallback);
for cluster in &picture.prim_list.clusters {
if cluster.flags.contains(ClusterFlags::IS_PICTURE) {
for instance in &cluster.prim_instances {
let child_pic_index = match instance.kind {
PrimitiveInstanceKind::Picture { pic_index, .. } => pic_index,
_ => unreachable!(),
};
self.assign_raster_roots(
child_pic_index,
picture_primitives,
new_fallback,
);
}
}
}
}
}
@ -2143,18 +2164,21 @@ pub struct OrderedPictureChild {
pub gpu_address: GpuCacheAddress,
}
/// Defines the grouping key for a cluster of primitives in a picture.
/// In future this will also contain spatial grouping details.
#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
struct PrimitiveClusterKey {
/// Grouping primitives by spatial node ensures that we can calculate a local
/// bounding volume for the cluster, and then transform that by the spatial
/// node transform once to get an updated bounding volume for the entire cluster.
spatial_node_index: SpatialNodeIndex,
/// We want to separate clusters that have different backface visibility properties
/// so that we can accept / reject an entire cluster at once if the backface is not
/// visible.
is_backface_visible: bool,
bitflags! {
/// A set of flags describing why a picture may need a backing surface.
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct ClusterFlags: u32 {
/// This cluster is a picture
const IS_PICTURE = 1;
/// Whether this cluster is visible when the position node is a backface.
const IS_BACKFACE_VISIBLE = 2;
/// This flag is set during the first pass picture traversal, depending on whether
/// the cluster is visible or not. It's read during the second pass when primitives
/// consult their owning clusters to see if the primitive itself is visible.
const IS_VISIBLE = 4;
/// Is a backdrop-filter cluster that requires special handling during post_update.
const IS_BACKDROP_FILTER = 8;
}
}
/// Descriptor for a cluster of primitives. For now, this is quite basic but will be
@ -2162,70 +2186,84 @@ struct PrimitiveClusterKey {
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct PrimitiveCluster {
/// The positioning node for this cluster.
spatial_node_index: SpatialNodeIndex,
/// Whether this cluster is visible when the position node is a backface.
is_backface_visible: bool,
pub spatial_node_index: SpatialNodeIndex,
/// The bounding rect of the cluster, in the local space of the spatial node.
/// This is used to quickly determine the overall bounding rect for a picture
/// during the first picture traversal, which is needed for local scale
/// determination, and render task size calculations.
bounding_rect: LayoutRect,
/// This flag is set during the first pass picture traversal, depending on whether
/// the cluster is visible or not. It's read during the second pass when primitives
/// consult their owning clusters to see if the primitive itself is visible.
pub is_visible: bool,
/// The list of primitive instances in this cluster.
pub prim_instances: Vec<PrimitiveInstance>,
/// Various flags / state for this cluster.
pub flags: ClusterFlags,
}
/// Where to insert a prim instance in a primitive list.
#[derive(Debug, Copy, Clone)]
enum PrimitiveListPosition {
Begin,
End,
}
impl PrimitiveCluster {
/// Construct a new primitive cluster for a given positioning node.
fn new(
spatial_node_index: SpatialNodeIndex,
is_backface_visible: bool,
flags: ClusterFlags,
) -> Self {
PrimitiveCluster {
bounding_rect: LayoutRect::zero(),
spatial_node_index,
is_backface_visible,
is_visible: false,
flags,
prim_instances: Vec::new(),
}
}
/// Return true if this cluster is compatible with the given params
pub fn is_compatible(
&self,
spatial_node_index: SpatialNodeIndex,
flags: ClusterFlags,
) -> bool {
self.flags == flags && self.spatial_node_index == spatial_node_index
}
/// Add a primitive instance to this cluster, at the start or end
fn push(
&mut self,
prim_instance: PrimitiveInstance,
prim_size: LayoutSize,
insert_position: PrimitiveListPosition,
) {
let prim_rect = LayoutRect::new(
prim_instance.prim_origin,
prim_size,
);
let culling_rect = prim_instance.local_clip_rect
.intersection(&prim_rect)
.unwrap_or_else(LayoutRect::zero);
self.bounding_rect = self.bounding_rect.union(&culling_rect);
match insert_position {
PrimitiveListPosition::Begin => {
self.prim_instances.insert(0, prim_instance);
}
PrimitiveListPosition::End => {
self.prim_instances.push(prim_instance);
}
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct PrimitiveClusterIndex(pub u32);
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct ClusterIndex(pub u16);
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct PrimitiveIndex(pub u32);
impl ClusterIndex {
pub const INVALID: ClusterIndex = ClusterIndex(u16::MAX);
}
/// A list of pictures, stored by the PrimitiveList to enable a
/// fast traversal of just the pictures.
pub type PictureList = SmallVec<[PictureIndex; 4]>;
/// A list of primitive instances that are added to a picture
/// This ensures we can keep a list of primitives that
/// are pictures, for a fast initial traversal of the picture
/// tree without walking the instance list.
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct PrimitiveList {
/// The primitive instances, in render order.
pub prim_instances: Vec<PrimitiveInstance>,
/// List of pictures that are part of this list.
/// Used to implement the picture traversal pass.
pub pictures: PictureList,
/// List of primitives grouped into clusters.
pub clusters: SmallVec<[PrimitiveCluster; 4]>,
/// List of primitive indicies that can only update
/// the cluster during frame building. This maps to
/// primitives in the prim_instances array.
pub deferred_prims: Vec<PrimitiveIndex>,
pub clusters: Vec<PrimitiveCluster>,
}
impl PrimitiveList {
@ -2235,145 +2273,126 @@ impl PrimitiveList {
/// picture traversal pass is completed.
pub fn empty() -> Self {
PrimitiveList {
prim_instances: Vec::new(),
pictures: SmallVec::new(),
clusters: SmallVec::new(),
deferred_prims: Vec::new(),
clusters: Vec::new(),
}
}
/// Construct a new prim list from a list of instances
/// in render order. This does some work during scene
/// building which makes the frame building traversals
/// significantly faster.
pub fn new(
mut prim_instances: Vec<PrimitiveInstance>,
interners: &Interners
) -> Self {
let mut pictures = SmallVec::new();
let mut clusters_map = FastHashMap::default();
let mut clusters: SmallVec<[PrimitiveCluster; 4]> = SmallVec::new();
let mut deferred_prims = Vec::new();
/// Add a primitive instance to this list, at the start or end
fn push(
&mut self,
prim_instance: PrimitiveInstance,
prim_size: LayoutSize,
spatial_node_index: SpatialNodeIndex,
is_backface_visible: bool,
insert_position: PrimitiveListPosition,
) {
let mut flags = ClusterFlags::empty();
// Walk the list of primitive instances and extract any that
// are pictures.
for (prim_index, prim_instance) in &mut prim_instances.iter_mut().enumerate() {
// Check if this primitive is a picture. In future we should
// remove this match and embed this info directly in the primitive instance.
let is_pic = match prim_instance.kind {
PrimitiveInstanceKind::Picture { pic_index, .. } => {
pictures.push(pic_index);
true
}
_ => {
false
}
};
let (is_backface_visible, prim_size) = match prim_instance.kind {
PrimitiveInstanceKind::Rectangle { data_handle, .. } |
PrimitiveInstanceKind::Clear { data_handle, .. } => {
let data = &interners.prim[data_handle];
(data.is_backface_visible, data.prim_size)
}
PrimitiveInstanceKind::Image { data_handle, .. } => {
let data = &interners.image[data_handle];
(data.is_backface_visible, data.prim_size)
}
PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
let data = &interners.image_border[data_handle];
(data.is_backface_visible, data.prim_size)
}
PrimitiveInstanceKind::LineDecoration { data_handle, .. } => {
let data = &interners.line_decoration[data_handle];
(data.is_backface_visible, data.prim_size)
}
PrimitiveInstanceKind::LinearGradient { data_handle, .. } => {
let data = &interners.linear_grad[data_handle];
(data.is_backface_visible, data.prim_size)
}
PrimitiveInstanceKind::NormalBorder { data_handle, .. } => {
let data = &interners.normal_border[data_handle];
(data.is_backface_visible, data.prim_size)
}
PrimitiveInstanceKind::Picture { data_handle, .. } => {
let data = &interners.picture[data_handle];
(data.is_backface_visible, data.prim_size)
}
PrimitiveInstanceKind::RadialGradient { data_handle, ..} => {
let data = &interners.radial_grad[data_handle];
(data.is_backface_visible, data.prim_size)
}
PrimitiveInstanceKind::TextRun { data_handle, .. } => {
let data = &interners.text_run[data_handle];
(data.is_backface_visible, data.prim_size)
}
PrimitiveInstanceKind::YuvImage { data_handle, .. } => {
let data = &interners.yuv_image[data_handle];
(data.is_backface_visible, data.prim_size)
}
PrimitiveInstanceKind::Backdrop { data_handle, .. } => {
// We don't know the actual size of the backdrop until frame
// building, so use an empty rect for now and add it to the
// deferred primitive list.
let data = &interners.backdrop[data_handle];
deferred_prims.push(PrimitiveIndex(prim_index as u32));
(data.is_backface_visible, LayoutSize::zero())
}
PrimitiveInstanceKind::PushClipChain |
PrimitiveInstanceKind::PopClipChain => {
(true, LayoutSize::zero())
}
};
// Get the key for the cluster that this primitive should
// belong to.
let key = PrimitiveClusterKey {
spatial_node_index: prim_instance.spatial_node_index,
is_backface_visible,
};
// Find the cluster, or create a new one.
let cluster_index = *clusters_map
.entry(key)
.or_insert_with(|| {
let index = clusters.len();
clusters.push(PrimitiveCluster::new(
prim_instance.spatial_node_index,
is_backface_visible,
));
index
}
);
if prim_instance.is_chased() {
println!("\tcluster {} with {:?}", cluster_index, key);
// Pictures are always put into a new cluster, to make it faster to
// iterate all pictures in a given primitive list.
match prim_instance.kind {
PrimitiveInstanceKind::Picture { .. } => {
flags.insert(ClusterFlags::IS_PICTURE);
}
// Pictures don't have a known static local bounding rect (they are
// calculated during the picture traversal dynamically). If not
// a picture, include a minimal bounding rect in the cluster bounds.
let cluster = &mut clusters[cluster_index];
if !is_pic {
let prim_rect = LayoutRect::new(
prim_instance.prim_origin,
prim_size,
);
let culling_rect = prim_instance.local_clip_rect
.intersection(&prim_rect)
.unwrap_or_else(LayoutRect::zero);
cluster.bounding_rect = cluster.bounding_rect.union(&culling_rect);
PrimitiveInstanceKind::Backdrop { .. } => {
flags.insert(ClusterFlags::IS_BACKDROP_FILTER);
}
prim_instance.cluster_index = ClusterIndex(cluster_index as u16);
_ => {}
}
if is_backface_visible {
flags.insert(ClusterFlags::IS_BACKFACE_VISIBLE);
}
// Insert the primitive into the first or last cluster as required
match insert_position {
PrimitiveListPosition::Begin => {
if let Some(cluster) = self.clusters.first_mut() {
if cluster.is_compatible(spatial_node_index, flags) {
cluster.push(prim_instance, prim_size, insert_position);
return;
}
}
let mut cluster = PrimitiveCluster::new(
spatial_node_index,
flags,
);
cluster.push(prim_instance, prim_size, insert_position);
self.clusters.insert(0, cluster);
}
PrimitiveListPosition::End => {
if let Some(cluster) = self.clusters.last_mut() {
if cluster.is_compatible(spatial_node_index, flags) {
cluster.push(prim_instance, prim_size, insert_position);
return;
}
}
let mut cluster = PrimitiveCluster::new(
spatial_node_index,
flags,
);
cluster.push(prim_instance, prim_size, insert_position);
self.clusters.push(cluster);
}
}
}
/// Add a primitive instance to the start of the list
pub fn add_prim_to_start(
&mut self,
prim_instance: PrimitiveInstance,
prim_size: LayoutSize,
spatial_node_index: SpatialNodeIndex,
is_backface_visible: bool,
) {
self.push(
prim_instance,
prim_size,
spatial_node_index,
is_backface_visible,
PrimitiveListPosition::Begin,
)
}
/// Add a primitive instance to the end of the list
pub fn add_prim(
&mut self,
prim_instance: PrimitiveInstance,
prim_size: LayoutSize,
spatial_node_index: SpatialNodeIndex,
is_backface_visible: bool,
) {
self.push(
prim_instance,
prim_size,
spatial_node_index,
is_backface_visible,
PrimitiveListPosition::End,
)
}
/// Returns true if there are no clusters (and thus primitives)
pub fn is_empty(&self) -> bool {
self.clusters.is_empty()
}
/// Merge another primitive list into this one
pub fn extend(&mut self, prim_list: PrimitiveList) {
self.clusters.extend(prim_list.clusters);
}
/// Return the number of clusters in this prim list
pub fn len(&self) -> usize {
self.clusters.len()
}
/// Split this primitive list at the given cluster index
pub fn split_off(&mut self, index: usize) -> PrimitiveList {
let clusters = self.clusters.split_off(index);
PrimitiveList {
prim_instances,
pictures,
clusters,
deferred_prims,
clusters
}
}
}
@ -2466,14 +2485,22 @@ impl PicturePrimitive {
pt: &mut T,
) {
pt.new_level(format!("{:?}", self_index));
pt.add_item(format!("prim_count: {:?}", self.prim_list.prim_instances.len()));
pt.add_item(format!("cluster_count: {:?}", self.prim_list.clusters.len()));
pt.add_item(format!("local_rect: {:?}", self.local_rect));
pt.add_item(format!("spatial_node_index: {:?}", self.spatial_node_index));
pt.add_item(format!("raster_config: {:?}", self.raster_config));
pt.add_item(format!("requested_composite_mode: {:?}", self.requested_composite_mode));
for index in &self.prim_list.pictures {
pictures[index.0].print(pictures, *index, pt);
for cluster in &self.prim_list.clusters {
if cluster.flags.contains(ClusterFlags::IS_PICTURE) {
for instance in &cluster.prim_instances {
let index = match instance.kind {
PrimitiveInstanceKind::Picture { pic_index, .. } => pic_index,
_ => unreachable!(),
};
pictures[index.0].print(pictures, index, pt);
}
}
}
pt.end_level();
@ -3290,8 +3317,8 @@ impl PicturePrimitive {
// Process the accumulated split planes and order them for rendering.
// Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) {
let prim_instance = &self.prim_list.prim_instances[poly.anchor.prim_instance_index];
let spatial_node_index = prim_instance.spatial_node_index;
let cluster = &self.prim_list.clusters[poly.anchor.cluster_index];
let spatial_node_index = cluster.spatial_node_index;
let transform = match clip_scroll_tree
.get_world_transform(spatial_node_index)
.inverse()
@ -3459,62 +3486,11 @@ impl PicturePrimitive {
// Pop the state information about this picture.
state.pop_picture();
// Update any primitives/cluster bounding rects that can only be done
// with information available during frame building.
for prim_index in &self.prim_list.deferred_prims {
let prim_instance = &mut self.prim_list.prim_instances[prim_index.0 as usize];
match prim_instance.kind {
PrimitiveInstanceKind::Backdrop { data_handle, .. } => {
// The actual size and clip rect of this primitive are determined by computing the bounding
// box of the projected rect of the backdrop-filter element onto the backdrop.
let prim_data = &mut data_stores.backdrop[data_handle];
let spatial_node_index = prim_data.kind.spatial_node_index;
// We cannot use the relative transform between the backdrop and the element because
// that doesn't take into account any projection transforms that both spatial nodes are children of.
// Instead, we first project from the element to the world space and get a flattened 2D bounding rect
// in the screen space, we then map this rect from the world space to the backdrop space to get the
// proper bounding box where the backdrop-filter needs to be processed.
let prim_to_world_mapper = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
spatial_node_index,
LayoutRect::max_rect(),
frame_context.clip_scroll_tree,
);
let backdrop_to_world_mapper = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
prim_instance.spatial_node_index,
LayoutRect::max_rect(),
frame_context.clip_scroll_tree,
);
// First map to the screen and get a flattened rect
let prim_rect = prim_to_world_mapper.map(&prim_data.kind.border_rect).unwrap_or_else(LayoutRect::zero);
// Backwards project the flattened rect onto the backdrop
let prim_rect = backdrop_to_world_mapper.unmap(&prim_rect).unwrap_or_else(LayoutRect::zero);
// TODO(aosmond): Is this safe? Updating the primitive size during
// frame building is usually problematic since scene building will cache
// the primitive information in the GPU already.
prim_instance.prim_origin = prim_rect.origin;
prim_data.common.prim_size = prim_rect.size;
prim_instance.local_clip_rect = prim_rect;
// Update the cluster bounding rect now that we have the backdrop rect.
let cluster = &mut self.prim_list.clusters[prim_instance.cluster_index.0 as usize];
cluster.bounding_rect = cluster.bounding_rect.union(&prim_rect);
}
_ => {
panic!("BUG: unexpected deferred primitive kind for cluster updates");
}
}
}
for cluster in &mut self.prim_list.clusters {
cluster.flags.remove(ClusterFlags::IS_VISIBLE);
// Skip the cluster if backface culled.
if !cluster.is_backface_visible {
if !cluster.flags.contains(ClusterFlags::IS_BACKFACE_VISIBLE) {
// For in-preserve-3d primitives and pictures, the backface visibility is
// evaluated relative to the containing block.
if let Picture3DContext::In { ancestor_index, .. } = self.context_3d {
@ -3536,6 +3512,59 @@ impl PicturePrimitive {
continue;
}
// Update any primitives/cluster bounding rects that can only be done
// with information available during frame building.
if cluster.flags.contains(ClusterFlags::IS_BACKDROP_FILTER) {
let backdrop_to_world_mapper = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
cluster.spatial_node_index,
LayoutRect::max_rect(),
frame_context.clip_scroll_tree,
);
for prim_instance in &mut cluster.prim_instances {
match prim_instance.kind {
PrimitiveInstanceKind::Backdrop { data_handle, .. } => {
// The actual size and clip rect of this primitive are determined by computing the bounding
// box of the projected rect of the backdrop-filter element onto the backdrop.
let prim_data = &mut data_stores.backdrop[data_handle];
let spatial_node_index = prim_data.kind.spatial_node_index;
// We cannot use the relative transform between the backdrop and the element because
// that doesn't take into account any projection transforms that both spatial nodes are children of.
// Instead, we first project from the element to the world space and get a flattened 2D bounding rect
// in the screen space, we then map this rect from the world space to the backdrop space to get the
// proper bounding box where the backdrop-filter needs to be processed.
let prim_to_world_mapper = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
spatial_node_index,
LayoutRect::max_rect(),
frame_context.clip_scroll_tree,
);
// First map to the screen and get a flattened rect
let prim_rect = prim_to_world_mapper.map(&prim_data.kind.border_rect).unwrap_or_else(LayoutRect::zero);
// Backwards project the flattened rect onto the backdrop
let prim_rect = backdrop_to_world_mapper.unmap(&prim_rect).unwrap_or_else(LayoutRect::zero);
// TODO(aosmond): Is this safe? Updating the primitive size during
// frame building is usually problematic since scene building will cache
// the primitive information in the GPU already.
prim_instance.prim_origin = prim_rect.origin;
prim_data.common.prim_size = prim_rect.size;
prim_instance.local_clip_rect = prim_rect;
// Update the cluster bounding rect now that we have the backdrop rect.
cluster.bounding_rect = cluster.bounding_rect.union(&prim_rect);
}
_ => {
panic!("BUG: unexpected deferred primitive kind for cluster updates");
}
}
}
}
// Map the cluster bounding rect into the space of the surface, and
// include it in the surface bounding rect.
let surface = state.current_surface_mut();
@ -3548,7 +3577,7 @@ impl PicturePrimitive {
// backface checks. In future, this will include spatial clustering
// which will allow the frame building code to skip most of the
// current per-primitive culling code.
cluster.is_visible = true;
cluster.flags.insert(ClusterFlags::IS_VISIBLE);
if let Some(cluster_rect) = surface.map_local_to_surface.map(&cluster.bounding_rect) {
surface.rect = surface.rect.union(&cluster_rect);
}

View File

@ -27,8 +27,8 @@ use crate::image::{Repetition};
use crate::intern;
use crate::internal_types::PlaneSplitAnchor;
use malloc_size_of::MallocSizeOf;
use crate::picture::{PictureCompositeMode, PicturePrimitive};
use crate::picture::{ClusterIndex, PrimitiveList, RecordedDirtyRegion, SurfaceIndex, RetainedTiles, RasterConfig};
use crate::picture::{PictureCompositeMode, PicturePrimitive, ClusterFlags};
use crate::picture::{PrimitiveList, RecordedDirtyRegion, SurfaceIndex, RetainedTiles, RasterConfig};
use crate::prim_store::backdrop::BackdropDataHandle;
use crate::prim_store::borders::{ImageBorderDataHandle, NormalBorderDataHandle};
use crate::prim_store::gradient::{GRADIENT_FP_STOPS, GradientCacheKey, GradientStopKey};
@ -1548,16 +1548,8 @@ pub struct PrimitiveInstance {
/// visibility scratch buffer. If not visible, INVALID.
pub visibility_info: PrimitiveVisibilityIndex,
/// The cluster that this primitive belongs to. This is used
/// for quickly culling out groups of primitives during the
/// initial picture traversal pass.
pub cluster_index: ClusterIndex,
/// ID of the clip chain that this primitive is clipped by.
pub clip_chain_id: ClipChainId,
/// ID of the spatial node that this primitive is positioned by.
pub spatial_node_index: SpatialNodeIndex,
}
impl PrimitiveInstance {
@ -1566,7 +1558,6 @@ impl PrimitiveInstance {
local_clip_rect: LayoutRect,
kind: PrimitiveInstanceKind,
clip_chain_id: ClipChainId,
spatial_node_index: SpatialNodeIndex,
) -> Self {
PrimitiveInstance {
prim_origin,
@ -1578,8 +1569,6 @@ impl PrimitiveInstance {
id: PrimitiveDebugId(NEXT_PRIM_ID.fetch_add(1, Ordering::Relaxed)),
visibility_info: PrimitiveVisibilityIndex::INVALID,
clip_chain_id,
spatial_node_index,
cluster_index: ClusterIndex::INVALID,
}
}
@ -1863,10 +1852,13 @@ impl PrimitiveStore {
/// Returns the total count of primitive instances contained in pictures.
pub fn prim_count(&self) -> usize {
self.pictures
.iter()
.map(|p| p.prim_list.prim_instances.len())
.sum()
let mut prim_count = 0;
for pic in &self.pictures {
for cluster in &pic.prim_list.clusters {
prim_count += cluster.prim_instances.len();
}
}
prim_count
}
/// Update visibility pass - update each primitive visibility struct, and
@ -1932,297 +1924,298 @@ impl PrimitiveStore {
frame_context.clip_scroll_tree,
);
for prim_instance in &mut prim_list.prim_instances {
prim_instance.reset();
if prim_instance.is_chased() {
#[cfg(debug_assertions)] // needed for ".id" part
println!("\tpreparing {:?} in {:?}", prim_instance.id, pic_index);
println!("\t{:?}", prim_instance.kind);
}
for cluster in &mut prim_list.clusters {
// Get the cluster and see if is visible
if !prim_list.clusters[prim_instance.cluster_index.0 as usize].is_visible {
if prim_instance.is_chased() {
println!("\tcluster is invisible");
}
if !cluster.flags.contains(ClusterFlags::IS_VISIBLE) {
continue;
}
map_local_to_surface.set_target_spatial_node(
prim_instance.spatial_node_index,
cluster.spatial_node_index,
frame_context.clip_scroll_tree,
);
let (is_passthrough, prim_local_rect, prim_shadow_rect) = match prim_instance.kind {
PrimitiveInstanceKind::PushClipChain => {
frame_state.clip_chain_stack.push_clip(
prim_instance.clip_chain_id,
frame_state.clip_store,
);
continue;
for prim_instance in &mut cluster.prim_instances {
prim_instance.reset();
if prim_instance.is_chased() {
#[cfg(debug_assertions)] // needed for ".id" part
println!("\tpreparing {:?} in {:?}", prim_instance.id, pic_index);
println!("\t{:?}", prim_instance.kind);
}
PrimitiveInstanceKind::PopClipChain => {
frame_state.clip_chain_stack.pop_clip();
continue;
}
PrimitiveInstanceKind::Picture { pic_index, .. } => {
if !self.pictures[pic_index.0].is_visible() {
let (is_passthrough, prim_local_rect, prim_shadow_rect) = match prim_instance.kind {
PrimitiveInstanceKind::PushClipChain => {
frame_state.clip_chain_stack.push_clip(
prim_instance.clip_chain_id,
frame_state.clip_store,
);
continue;
}
frame_state.clip_chain_stack.push_clip(
prim_instance.clip_chain_id,
frame_state.clip_store,
);
self.update_visibility(
pic_index,
surface_index,
&world_culling_rect,
frame_context,
frame_state,
);
frame_state.clip_chain_stack.pop_clip();
// The local rect of pictures is calculated dynamically based on
// the content of children, which may move due to the spatial
// node they are attached to. Other parts of the code (such as
// segment generation) reads the origin from the prim instance,
// so ensure that is kept up to date here.
// TODO(gw): It's unfortunate that the prim origin is duplicated
// this way. In future, we could perhaps just store the
// size in the picture primitive, to that there isn't
// any duplicated data.
let pic = &self.pictures[pic_index.0];
prim_instance.prim_origin = pic.local_rect.origin;
let shadow_rect = if let Some(RasterConfig { composite_mode: PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)), .. }) = pic.raster_config {
// If we have a drop shadow filter, we also need to include the shadow in
// our local rect for the purpose of calculating the size of the picture.
let mut rect = LayoutRect::zero();
for shadow in shadows {
rect = rect.union(&pic.local_rect.translate(shadow.offset));
}
rect
} else {
LayoutRect::zero()
};
(pic.raster_config.is_none(), pic.local_rect, shadow_rect)
}
_ => {
let prim_data = &frame_state.data_stores.as_common_data(&prim_instance);
let prim_rect = LayoutRect::new(
prim_instance.prim_origin,
prim_data.prim_size,
);
(false, prim_rect, LayoutRect::zero())
}
};
if is_passthrough {
let vis_index = PrimitiveVisibilityIndex(frame_state.scratch.prim_info.len() as u32);
frame_state.scratch.prim_info.push(
PrimitiveVisibility {
clipped_world_rect: WorldRect::max_rect(),
clip_chain: ClipChainInstance::empty(),
clip_task_index: ClipTaskIndex::INVALID,
combined_local_clip_rect: LayoutRect::zero(),
visibility_mask: PrimitiveVisibilityMask::empty(),
}
);
prim_instance.visibility_info = vis_index;
} else {
if prim_local_rect.size.width <= 0.0 || prim_local_rect.size.height <= 0.0 {
if prim_instance.is_chased() {
println!("\tculled for zero local rectangle");
}
continue;
}
// Inflate the local rect for this primitive by the inflation factor of
// the picture context and include the shadow offset. This ensures that
// even if the primitive itself is not visible, any effects from the
// blur radius or shadow will be correctly taken into account.
let inflation_factor = surface.inflation_factor;
let local_rect = prim_local_rect
.inflate(inflation_factor, inflation_factor)
.union(&prim_shadow_rect)
.intersection(&prim_instance.local_clip_rect);
let local_rect = match local_rect {
Some(local_rect) => local_rect,
None => {
if prim_instance.is_chased() {
println!("\tculled for being out of the local clip rectangle: {:?}",
prim_instance.local_clip_rect);
}
continue;
}
};
// Include the clip chain for this primitive in the current stack.
frame_state.clip_chain_stack.push_clip(
prim_instance.clip_chain_id,
frame_state.clip_store,
);
frame_state.clip_store.set_active_clips(
prim_instance.local_clip_rect,
prim_instance.spatial_node_index,
frame_state.clip_chain_stack.current_clips_array(),
&frame_context.clip_scroll_tree,
&mut frame_state.data_stores.clip,
);
let clip_chain = frame_state
.clip_store
.build_clip_chain_instance(
local_rect,
&map_local_to_surface,
&map_surface_to_world,
&frame_context.clip_scroll_tree,
frame_state.gpu_cache,
frame_state.resource_cache,
surface.device_pixel_scale,
&world_culling_rect,
&mut frame_state.data_stores.clip,
true,
prim_instance.is_chased(),
);
if let Some(ref mut tile_cache) = frame_state.tile_cache {
if !tile_cache.update_prim_dependencies(
prim_instance,
clip_chain.as_ref(),
prim_local_rect,
frame_context.clip_scroll_tree,
frame_state.data_stores,
frame_state.clip_store,
&self.pictures,
frame_state.resource_cache,
&self.opacity_bindings,
&self.images,
surface_index,
) {
prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
// Ensure the primitive clip is popped - perhaps we can use
// some kind of scope to do this automatically in future.
PrimitiveInstanceKind::PopClipChain => {
frame_state.clip_chain_stack.pop_clip();
continue;
}
}
PrimitiveInstanceKind::Picture { pic_index, .. } => {
if !self.pictures[pic_index.0].is_visible() {
continue;
}
// Ensure the primitive clip is popped
frame_state.clip_chain_stack.pop_clip();
frame_state.clip_chain_stack.push_clip(
prim_instance.clip_chain_id,
frame_state.clip_store,
);
let clip_chain = match clip_chain {
Some(clip_chain) => clip_chain,
None => {
self.update_visibility(
pic_index,
surface_index,
&world_culling_rect,
frame_context,
frame_state,
);
frame_state.clip_chain_stack.pop_clip();
// The local rect of pictures is calculated dynamically based on
// the content of children, which may move due to the spatial
// node they are attached to. Other parts of the code (such as
// segment generation) reads the origin from the prim instance,
// so ensure that is kept up to date here.
// TODO(gw): It's unfortunate that the prim origin is duplicated
// this way. In future, we could perhaps just store the
// size in the picture primitive, to that there isn't
// any duplicated data.
let pic = &self.pictures[pic_index.0];
prim_instance.prim_origin = pic.local_rect.origin;
let shadow_rect = if let Some(RasterConfig { composite_mode: PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)), .. }) = pic.raster_config {
// If we have a drop shadow filter, we also need to include the shadow in
// our local rect for the purpose of calculating the size of the picture.
let mut rect = LayoutRect::zero();
for shadow in shadows {
rect = rect.union(&pic.local_rect.translate(shadow.offset));
}
rect
} else {
LayoutRect::zero()
};
(pic.raster_config.is_none(), pic.local_rect, shadow_rect)
}
_ => {
let prim_data = &frame_state.data_stores.as_common_data(&prim_instance);
let prim_rect = LayoutRect::new(
prim_instance.prim_origin,
prim_data.prim_size,
);
(false, prim_rect, LayoutRect::zero())
}
};
if is_passthrough {
let vis_index = PrimitiveVisibilityIndex(frame_state.scratch.prim_info.len() as u32);
frame_state.scratch.prim_info.push(
PrimitiveVisibility {
clipped_world_rect: WorldRect::max_rect(),
clip_chain: ClipChainInstance::empty(),
clip_task_index: ClipTaskIndex::INVALID,
combined_local_clip_rect: LayoutRect::zero(),
visibility_mask: PrimitiveVisibilityMask::empty(),
}
);
prim_instance.visibility_info = vis_index;
} else {
if prim_local_rect.size.width <= 0.0 || prim_local_rect.size.height <= 0.0 {
if prim_instance.is_chased() {
println!("\tunable to build the clip chain, skipping");
println!("\tculled for zero local rectangle");
}
continue;
}
// Inflate the local rect for this primitive by the inflation factor of
// the picture context and include the shadow offset. This ensures that
// even if the primitive itself is not visible, any effects from the
// blur radius or shadow will be correctly taken into account.
let inflation_factor = surface.inflation_factor;
let local_rect = prim_local_rect
.inflate(inflation_factor, inflation_factor)
.union(&prim_shadow_rect)
.intersection(&prim_instance.local_clip_rect);
let local_rect = match local_rect {
Some(local_rect) => local_rect,
None => {
if prim_instance.is_chased() {
println!("\tculled for being out of the local clip rectangle: {:?}",
prim_instance.local_clip_rect);
}
continue;
}
};
// Include the clip chain for this primitive in the current stack.
frame_state.clip_chain_stack.push_clip(
prim_instance.clip_chain_id,
frame_state.clip_store,
);
frame_state.clip_store.set_active_clips(
prim_instance.local_clip_rect,
cluster.spatial_node_index,
frame_state.clip_chain_stack.current_clips_array(),
&frame_context.clip_scroll_tree,
&mut frame_state.data_stores.clip,
);
let clip_chain = frame_state
.clip_store
.build_clip_chain_instance(
local_rect,
&map_local_to_surface,
&map_surface_to_world,
&frame_context.clip_scroll_tree,
frame_state.gpu_cache,
frame_state.resource_cache,
surface.device_pixel_scale,
&world_culling_rect,
&mut frame_state.data_stores.clip,
true,
prim_instance.is_chased(),
);
if let Some(ref mut tile_cache) = frame_state.tile_cache {
if !tile_cache.update_prim_dependencies(
prim_instance,
cluster.spatial_node_index,
clip_chain.as_ref(),
prim_local_rect,
frame_context.clip_scroll_tree,
frame_state.data_stores,
frame_state.clip_store,
&self.pictures,
frame_state.resource_cache,
&self.opacity_bindings,
&self.images,
surface_index,
) {
prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
// Ensure the primitive clip is popped - perhaps we can use
// some kind of scope to do this automatically in future.
frame_state.clip_chain_stack.pop_clip();
continue;
}
}
// Ensure the primitive clip is popped
frame_state.clip_chain_stack.pop_clip();
let clip_chain = match clip_chain {
Some(clip_chain) => clip_chain,
None => {
if prim_instance.is_chased() {
println!("\tunable to build the clip chain, skipping");
}
prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
continue;
}
};
if prim_instance.is_chased() {
println!("\teffective clip chain from {:?} {}",
clip_chain.clips_range,
if apply_local_clip_rect { "(applied)" } else { "" },
);
println!("\tpicture rect {:?} @{:?}",
clip_chain.pic_clip_rect,
clip_chain.pic_spatial_node_index,
);
}
// Check if the clip bounding rect (in pic space) is visible on screen
// This includes both the prim bounding rect + local prim clip rect!
let world_rect = match map_surface_to_world.map(&clip_chain.pic_clip_rect) {
Some(world_rect) => world_rect,
None => {
continue;
}
};
let clipped_world_rect = match world_rect.intersection(&world_culling_rect) {
Some(rect) => rect,
None => {
continue;
}
};
let combined_local_clip_rect = if apply_local_clip_rect {
clip_chain.local_clip_rect
} else {
prim_instance.local_clip_rect
};
if combined_local_clip_rect.size.is_empty_or_negative() {
debug_assert!(combined_local_clip_rect.size.width >= 0.0 &&
combined_local_clip_rect.size.height >= 0.0);
if prim_instance.is_chased() {
println!("\tculled for zero local clip rectangle");
}
prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
continue;
}
};
if prim_instance.is_chased() {
println!("\teffective clip chain from {:?} {}",
clip_chain.clips_range,
if apply_local_clip_rect { "(applied)" } else { "" },
);
println!("\tpicture rect {:?} @{:?}",
clip_chain.pic_clip_rect,
clip_chain.pic_spatial_node_index,
);
}
// Check if the clip bounding rect (in pic space) is visible on screen
// This includes both the prim bounding rect + local prim clip rect!
let world_rect = match map_surface_to_world.map(&clip_chain.pic_clip_rect) {
Some(world_rect) => world_rect,
None => {
continue;
// When the debug display is enabled, paint a colored rectangle around each
// primitive.
if frame_context.debug_flags.contains(::api::DebugFlags::PRIMITIVE_DBG) {
let debug_color = match prim_instance.kind {
PrimitiveInstanceKind::PushClipChain |
PrimitiveInstanceKind::PopClipChain |
PrimitiveInstanceKind::Picture { .. } => ColorF::TRANSPARENT,
PrimitiveInstanceKind::TextRun { .. } => debug_colors::RED,
PrimitiveInstanceKind::LineDecoration { .. } => debug_colors::PURPLE,
PrimitiveInstanceKind::NormalBorder { .. } |
PrimitiveInstanceKind::ImageBorder { .. } => debug_colors::ORANGE,
PrimitiveInstanceKind::Rectangle { .. } => ColorF { r: 0.8, g: 0.8, b: 0.8, a: 0.5 },
PrimitiveInstanceKind::YuvImage { .. } => debug_colors::BLUE,
PrimitiveInstanceKind::Image { .. } => debug_colors::BLUE,
PrimitiveInstanceKind::LinearGradient { .. } => debug_colors::PINK,
PrimitiveInstanceKind::RadialGradient { .. } => debug_colors::PINK,
PrimitiveInstanceKind::Clear { .. } => debug_colors::CYAN,
PrimitiveInstanceKind::Backdrop { .. } => debug_colors::MEDIUMAQUAMARINE,
};
if debug_color.a != 0.0 {
let debug_rect = clipped_world_rect * frame_context.global_device_pixel_scale;
frame_state.scratch.push_debug_rect(debug_rect, debug_color);
}
}
};
let clipped_world_rect = match world_rect.intersection(&world_culling_rect) {
Some(rect) => rect,
None => {
continue;
}
};
let combined_local_clip_rect = if apply_local_clip_rect {
clip_chain.local_clip_rect
} else {
prim_instance.local_clip_rect
};
if combined_local_clip_rect.size.is_empty_or_negative() {
debug_assert!(combined_local_clip_rect.size.width >= 0.0 &&
combined_local_clip_rect.size.height >= 0.0);
let vis_index = PrimitiveVisibilityIndex(frame_state.scratch.prim_info.len() as u32);
if prim_instance.is_chased() {
println!("\tculled for zero local clip rectangle");
println!("\tvisible {:?} with {:?}", vis_index, combined_local_clip_rect);
}
prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
continue;
}
// When the debug display is enabled, paint a colored rectangle around each
// primitive.
if frame_context.debug_flags.contains(::api::DebugFlags::PRIMITIVE_DBG) {
let debug_color = match prim_instance.kind {
PrimitiveInstanceKind::PushClipChain |
PrimitiveInstanceKind::PopClipChain |
PrimitiveInstanceKind::Picture { .. } => ColorF::TRANSPARENT,
PrimitiveInstanceKind::TextRun { .. } => debug_colors::RED,
PrimitiveInstanceKind::LineDecoration { .. } => debug_colors::PURPLE,
PrimitiveInstanceKind::NormalBorder { .. } |
PrimitiveInstanceKind::ImageBorder { .. } => debug_colors::ORANGE,
PrimitiveInstanceKind::Rectangle { .. } => ColorF { r: 0.8, g: 0.8, b: 0.8, a: 0.5 },
PrimitiveInstanceKind::YuvImage { .. } => debug_colors::BLUE,
PrimitiveInstanceKind::Image { .. } => debug_colors::BLUE,
PrimitiveInstanceKind::LinearGradient { .. } => debug_colors::PINK,
PrimitiveInstanceKind::RadialGradient { .. } => debug_colors::PINK,
PrimitiveInstanceKind::Clear { .. } => debug_colors::CYAN,
PrimitiveInstanceKind::Backdrop { .. } => debug_colors::MEDIUMAQUAMARINE,
};
if debug_color.a != 0.0 {
let debug_rect = clipped_world_rect * frame_context.global_device_pixel_scale;
frame_state.scratch.push_debug_rect(debug_rect, debug_color);
}
}
frame_state.scratch.prim_info.push(
PrimitiveVisibility {
clipped_world_rect,
clip_chain,
clip_task_index: ClipTaskIndex::INVALID,
combined_local_clip_rect,
visibility_mask: PrimitiveVisibilityMask::empty(),
}
);
let vis_index = PrimitiveVisibilityIndex(frame_state.scratch.prim_info.len() as u32);
if prim_instance.is_chased() {
println!("\tvisible {:?} with {:?}", vis_index, combined_local_clip_rect);
}
prim_instance.visibility_info = vis_index;
frame_state.scratch.prim_info.push(
PrimitiveVisibility {
self.request_resources_for_prim(
prim_instance,
cluster.spatial_node_index,
clipped_world_rect,
clip_chain,
clip_task_index: ClipTaskIndex::INVALID,
combined_local_clip_rect,
visibility_mask: PrimitiveVisibilityMask::empty(),
}
);
prim_instance.visibility_info = vis_index;
self.request_resources_for_prim(
prim_instance,
clipped_world_rect,
frame_context,
frame_state,
);
frame_context,
frame_state,
);
}
}
}
@ -2259,6 +2252,7 @@ impl PrimitiveStore {
fn request_resources_for_prim(
&mut self,
prim_instance: &mut PrimitiveInstance,
prim_spatial_node_index: SpatialNodeIndex,
prim_world_rect: WorldRect,
frame_context: &FrameVisibilityContext,
frame_state: &mut FrameVisibilityState,
@ -2314,7 +2308,7 @@ impl PrimitiveStore {
let map_local_to_world = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
prim_instance.spatial_node_index,
prim_spatial_node_index,
frame_context.global_screen_world_rect,
frame_context.clip_scroll_tree,
);
@ -2416,11 +2410,16 @@ impl PrimitiveStore {
// We can only collapse opacity if there is a single primitive, otherwise
// the opacity needs to be applied to the primitives as a group.
if pic.prim_list.prim_instances.len() != 1 {
if pic.prim_list.clusters.len() != 1 {
return None;
}
let prim_instance = &pic.prim_list.prim_instances[0];
let cluster = &pic.prim_list.clusters[0];
if cluster.prim_instances.len() != 1 {
return None;
}
let prim_instance = &cluster.prim_instances[0];
// For now, we only support opacity collapse on solid rects and images.
// This covers the most common types of opacity filters that can be
@ -2484,7 +2483,7 @@ impl PrimitiveStore {
match self.get_opacity_collapse_prim(pic_index) {
Some(pic_index) => {
let pic = &mut self.pictures[pic_index.0];
let prim_instance = &mut pic.prim_list.prim_instances[0];
let prim_instance = &mut pic.prim_list.clusters[0].prim_instances[0];
match prim_instance.kind {
PrimitiveInstanceKind::Image { image_instance_index, .. } => {
let image_instance = &mut self.images[image_instance_index];
@ -2526,6 +2525,7 @@ impl PrimitiveStore {
pub fn prepare_prim_for_render(
&mut self,
prim_instance: &mut PrimitiveInstance,
prim_spatial_node_index: SpatialNodeIndex,
pic_context: &PictureContext,
pic_state: &mut PictureState,
frame_context: &FrameBuildingContext,
@ -2620,6 +2620,7 @@ impl PrimitiveStore {
if !is_passthrough {
prim_instance.update_clip_task(
prim_spatial_node_index,
pic_context.raster_spatial_node_index,
pic_context,
pic_state,
@ -2642,6 +2643,7 @@ impl PrimitiveStore {
self.prepare_interned_prim_for_render(
prim_instance,
prim_spatial_node_index,
plane_split_anchor,
pic_context,
pic_state,
@ -2664,52 +2666,53 @@ impl PrimitiveStore {
data_stores: &mut DataStores,
scratch: &mut PrimitiveScratchBuffer,
) {
for (prim_instance_index, prim_instance) in prim_list.prim_instances.iter_mut().enumerate() {
if prim_instance.visibility_info == PrimitiveVisibilityIndex::INVALID {
continue;
}
// The original clipped world rect was calculated during the initial visibility pass.
// However, it's possible that the dirty rect has got smaller, if tiles were not
// dirty. Intersecting with the dirty rect here eliminates preparing any primitives
// outside the dirty rect, and reduces the size of any off-screen surface allocations
// for clip masks / render tasks that we make.
{
let visibility_info = &mut scratch.prim_info[prim_instance.visibility_info.0 as usize];
let dirty_region = frame_state.current_dirty_region();
for dirty_region in &dirty_region.dirty_rects {
if visibility_info.clipped_world_rect.intersects(&dirty_region.world_rect) {
visibility_info.visibility_mask.include(dirty_region.visibility_mask);
}
}
if visibility_info.visibility_mask.is_empty() {
prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
continue;
}
}
for (cluster_index, cluster) in prim_list.clusters.iter_mut().enumerate() {
pic_state.map_local_to_pic.set_target_spatial_node(
prim_instance.spatial_node_index,
cluster.spatial_node_index,
frame_context.clip_scroll_tree,
);
let plane_split_anchor = PlaneSplitAnchor {
prim_instance_index,
};
for (prim_instance_index, prim_instance) in cluster.prim_instances.iter_mut().enumerate() {
if prim_instance.visibility_info == PrimitiveVisibilityIndex::INVALID {
continue;
}
if self.prepare_prim_for_render(
prim_instance,
pic_context,
pic_state,
frame_context,
frame_state,
plane_split_anchor,
data_stores,
scratch,
) {
frame_state.profile_counters.visible_primitives.inc();
// The original clipped world rect was calculated during the initial visibility pass.
// However, it's possible that the dirty rect has got smaller, if tiles were not
// dirty. Intersecting with the dirty rect here eliminates preparing any primitives
// outside the dirty rect, and reduces the size of any off-screen surface allocations
// for clip masks / render tasks that we make.
{
let visibility_info = &mut scratch.prim_info[prim_instance.visibility_info.0 as usize];
let dirty_region = frame_state.current_dirty_region();
for dirty_region in &dirty_region.dirty_rects {
if visibility_info.clipped_world_rect.intersects(&dirty_region.world_rect) {
visibility_info.visibility_mask.include(dirty_region.visibility_mask);
}
}
if visibility_info.visibility_mask.is_empty() {
prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
continue;
}
}
let plane_split_anchor = PlaneSplitAnchor::new(cluster_index, prim_instance_index);
if self.prepare_prim_for_render(
prim_instance,
cluster.spatial_node_index,
pic_context,
pic_state,
frame_context,
frame_state,
plane_split_anchor,
data_stores,
scratch,
) {
frame_state.profile_counters.visible_primitives.inc();
}
}
}
}
@ -2720,6 +2723,7 @@ impl PrimitiveStore {
fn prepare_interned_prim_for_render(
&mut self,
prim_instance: &mut PrimitiveInstance,
prim_spatial_node_index: SpatialNodeIndex,
plane_split_anchor: PlaneSplitAnchor,
pic_context: &PictureContext,
pic_state: &mut PictureState,
@ -2791,7 +2795,7 @@ impl PrimitiveStore {
// It's relative to the rasterizing space of a glyph.
let transform = frame_context.clip_scroll_tree
.get_relative_transform(
prim_instance.spatial_node_index,
prim_spatial_node_index,
pic_context.raster_spatial_node_index,
)
.into_fast_transform();
@ -2850,7 +2854,7 @@ impl PrimitiveStore {
// that will need to be accounted for here.
let scale = frame_context
.clip_scroll_tree
.get_world_transform(prim_instance.spatial_node_index)
.get_world_transform(prim_spatial_node_index)
.scale_factors();
// Scale factors are normalized to a power of 2 to reduce the number of
@ -3112,7 +3116,7 @@ impl PrimitiveStore {
let map_local_to_world = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
prim_instance.spatial_node_index,
prim_spatial_node_index,
frame_context.global_screen_world_rect,
frame_context.clip_scroll_tree,
);
@ -3174,7 +3178,7 @@ impl PrimitiveStore {
let map_local_to_world = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
prim_instance.spatial_node_index,
prim_spatial_node_index,
frame_context.global_screen_world_rect,
frame_context.clip_scroll_tree,
);
@ -3229,7 +3233,7 @@ impl PrimitiveStore {
PicturePrimitive::add_split_plane(
splitter,
frame_context.clip_scroll_tree,
prim_instance.spatial_node_index,
prim_spatial_node_index,
pic.local_rect,
&prim_info.combined_local_clip_rect,
frame_state.current_dirty_region().combined,
@ -3656,6 +3660,7 @@ impl PrimitiveInstance {
fn update_clip_task_for_brush(
&self,
prim_info: &mut PrimitiveVisibility,
prim_spatial_node_index: SpatialNodeIndex,
root_spatial_node_index: SpatialNodeIndex,
pic_context: &PictureContext,
pic_state: &mut PictureState,
@ -3791,7 +3796,7 @@ impl PrimitiveInstance {
// clip the segment completely.
frame_state.clip_store.set_active_clips_from_clip_chain(
&prim_info.clip_chain,
self.spatial_node_index,
prim_spatial_node_index,
&frame_context.clip_scroll_tree,
&data_stores.clip,
);
@ -3833,6 +3838,7 @@ impl PrimitiveInstance {
fn update_clip_task(
&mut self,
prim_spatial_node_index: SpatialNodeIndex,
root_spatial_node_index: SpatialNodeIndex,
pic_context: &PictureContext,
pic_state: &mut PictureState,
@ -3871,6 +3877,7 @@ impl PrimitiveInstance {
// First try to render this primitive's mask using optimized brush rendering.
if self.update_clip_task_for_brush(
prim_info,
prim_spatial_node_index,
root_spatial_node_index,
pic_context,
pic_state,
@ -4102,7 +4109,7 @@ fn test_struct_sizes() {
// test expectations and move on.
// (b) You made a structure larger. This is not necessarily a problem, but should only
// be done with care, and after checking if talos performance regresses badly.
assert_eq!(mem::size_of::<PrimitiveInstance>(), 96, "PrimitiveInstance size changed");
assert_eq!(mem::size_of::<PrimitiveInstance>(), 88, "PrimitiveInstance size changed");
assert_eq!(mem::size_of::<PrimitiveInstanceKind>(), 40, "PrimitiveInstanceKind size changed");
assert_eq!(mem::size_of::<PrimitiveTemplate>(), 40, "PrimitiveTemplate size changed");
assert_eq!(mem::size_of::<PrimitiveTemplateKind>(), 20, "PrimitiveTemplateKind size changed");