Bug 1559979 - Restructure the WR picture caching code into Tile methods. r=kvark

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Glenn Watson 2019-08-11 23:52:35 +00:00
Родитель c338fe4e07
Коммит 1dff77fb01
1 изменённых файлов: 421 добавлений и 263 удалений

Просмотреть файл

@ -223,6 +223,114 @@ impl From<PropertyBinding<f32>> for OpacityBinding {
} }
} }
// Immutable context passed to picture cache tiles during pre_update
struct TilePreUpdateContext<'a> {
/// The local rect of the overall picture cache
local_rect: PictureRect,
/// Maps from picture cache coords -> world space coords.
pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
/// If true, the fractional position of the picture cache changed,
/// requiring invalidation of all tiles.
fract_changed: bool,
/// Information about opacity bindings from the picture cache.
opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
}
// Immutable context passed to picture cache tiles during post_update
struct TilePostUpdateContext<'a> {
/// Current set of WR debug flags
debug_flags: DebugFlags,
/// The global scale factor from world -> device coords.
global_device_pixel_scale: DevicePixelScale,
/// The visible part of the screen in world coords.
global_screen_world_rect: WorldRect,
/// Current state of transforms
clip_scroll_tree: &'a ClipScrollTree,
/// The calculated opaque rect of the picture cache.
opaque_rect: PictureRect,
/// The spatial node of the picture cache.
cache_spatial_node_index: SpatialNodeIndex,
}
// Mutable state passed to picture cache tiles during post_update
struct TilePostUpdateState<'a> {
/// Scratch buffer for drawing debug information.
scratch: &'a mut PrimitiveScratchBuffer,
/// Current dirty region of this cache.
dirty_region: &'a mut DirtyRegion,
/// Allow access to the texture cache for requesting tiles
resource_cache: &'a mut ResourceCache,
/// Needed when requesting tile cache handles.
gpu_cache: &'a mut GpuCache,
}
/// Information about the dependencies of a single primitive instance.
struct PrimitiveDependencyInfo {
/// If true, this instance can be cached.
is_cacheable: bool,
/// If true, we should clip the prim rect to the tile boundaries.
clip_by_tile: bool,
/// Unique content identifier of the primitive.
prim_uid: ItemUid,
/// Defines the spatial node for this primitive.
prim_spatial_node_index: SpatialNodeIndex,
/// The (conservative) area in picture space this primitive occupies.
prim_rect: PictureRect,
/// The (conservative) clipped area in picture space this primitive occupies.
prim_clip_rect: PictureRect,
/// Image keys this primitive depends on.
image_keys: SmallVec<[ImageKey; 8]>,
/// Opacity bindings this primitive depends on.
opacity_bindings: SmallVec<[OpacityBinding; 4]>,
/// Clips that this primitive depends on.
clips: SmallVec<[ClipDescriptor; 8]>,
/// Spatial nodes references by the clip dependencies of this primitive.
clip_spatial_nodes: FastHashSet<SpatialNodeIndex>,
}
impl PrimitiveDependencyInfo {
/// Construct dependency info for a new primitive.
fn new(
prim_uid: ItemUid,
prim_rect: PictureRect,
prim_spatial_node_index: SpatialNodeIndex,
is_cacheable: bool,
) -> Self {
PrimitiveDependencyInfo {
prim_uid,
prim_rect,
prim_spatial_node_index,
is_cacheable,
image_keys: SmallVec::new(),
opacity_bindings: SmallVec::new(),
clip_by_tile: false,
prim_clip_rect: PictureRect::zero(),
clips: SmallVec::new(),
clip_spatial_nodes: FastHashSet::default(),
}
}
}
/// A stable ID for a given tile, to help debugging. /// A stable ID for a given tile, to help debugging.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub struct TileId(usize); pub struct TileId(usize);
@ -281,12 +389,6 @@ impl Tile {
} }
} }
/// Clear the dependencies for a tile.
fn clear(&mut self) {
self.transforms.clear();
self.descriptor.clear();
}
/// Invalidate a tile based on change in content. This /// Invalidate a tile based on change in content. This
/// must be called even if the tile is not currently /// must be called even if the tile is not currently
/// visible on screen. We might be able to improve this /// visible on screen. We might be able to improve this
@ -297,6 +399,262 @@ impl Tile {
self.is_same_content &= self.descriptor.is_same_content(self.id); self.is_same_content &= self.descriptor.is_same_content(self.id);
self.is_valid &= self.is_same_content; self.is_valid &= self.is_same_content;
} }
/// Called during pre_update of a tile cache instance. Allows the
/// tile to setup state before primitive dependency calculations.
fn pre_update(
&mut self,
rect: PictureRect,
ctx: &TilePreUpdateContext,
) {
self.rect = rect;
self.clipped_rect = self.rect
.intersection(&ctx.local_rect)
.unwrap_or(PictureRect::zero());
self.world_rect = ctx.pic_to_world_mapper
.map(&self.rect)
.expect("bug: map local tile rect");
// Do tile invalidation for any dependencies that we know now.
// Start frame assuming that the tile has the same content,
// unless the fractional offset of the transform root changed.
self.is_same_content = !ctx.fract_changed;
// Content has changed if any opacity bindings changed.
for binding in self.descriptor.opacity_bindings.items() {
if let OpacityBinding::Binding(id) = binding {
let changed = match ctx.opacity_bindings.get(id) {
Some(info) => info.changed,
None => true,
};
if changed {
self.is_same_content = false;
break;
}
}
}
// Clear any dependencies so that when we rebuild them we
// can compare if the tile has the same content.
self.transforms.clear();
self.descriptor.clear();
}
/// Add dependencies for a given primitive to this tile.
fn add_prim_dependency(
&mut self,
info: &PrimitiveDependencyInfo,
cache_spatial_node_index: SpatialNodeIndex,
) {
// Mark if the tile is cacheable at all.
self.is_same_content &= info.is_cacheable;
// Include any image keys this tile depends on.
self.descriptor.image_keys.extend_from_slice(&info.image_keys);
// Include any opacity bindings this primitive depends on.
self.descriptor.opacity_bindings.extend_from_slice(&info.opacity_bindings);
// TODO(gw): The origin of background rects produced by APZ changes
// in Gecko during scrolling. Consider investigating this so the
// hack / workaround below is not required.
let (prim_origin, prim_clip_rect) = if info.clip_by_tile {
let tile_p0 = self.clipped_rect.origin;
let tile_p1 = self.clipped_rect.bottom_right();
let clip_p0 = PicturePoint::new(
clampf(info.prim_clip_rect.origin.x, tile_p0.x, tile_p1.x),
clampf(info.prim_clip_rect.origin.y, tile_p0.y, tile_p1.y),
);
let clip_p1 = PicturePoint::new(
clampf(info.prim_clip_rect.origin.x + info.prim_clip_rect.size.width, tile_p0.x, tile_p1.x),
clampf(info.prim_clip_rect.origin.y + info.prim_clip_rect.size.height, tile_p0.y, tile_p1.y),
);
(
PicturePoint::new(
clampf(info.prim_rect.origin.x, tile_p0.x, tile_p1.x),
clampf(info.prim_rect.origin.y, tile_p0.y, tile_p1.y),
),
PictureRect::new(
clip_p0,
PictureSize::new(
clip_p1.x - clip_p0.x,
clip_p1.y - clip_p0.y,
),
),
)
} else {
(info.prim_rect.origin, info.prim_clip_rect)
};
// Update the tile descriptor, used for tile comparison during scene swaps.
self.descriptor.prims.push(PrimitiveDescriptor {
prim_uid: info.prim_uid,
origin: prim_origin.into(),
first_clip: self.descriptor.clips.len() as u16,
clip_count: info.clips.len() as u16,
prim_clip_rect: prim_clip_rect.into(),
});
self.descriptor.clips.extend_from_slice(&info.clips);
// If the primitive has the same spatial node, the relative transform
// will always be the same, so there's no need to depend on it.
if info.prim_spatial_node_index != cache_spatial_node_index {
self.transforms.insert(info.prim_spatial_node_index);
}
for spatial_node_index in &info.clip_spatial_nodes {
self.transforms.insert(*spatial_node_index);
}
}
/// Called during tile cache instance post_update. Allows invalidation and dirty
/// rect calculation after primitive dependencies have been updated.
fn post_update(
&mut self,
ctx: &TilePostUpdateContext,
state: &mut TilePostUpdateState,
) -> bool {
// Check if this tile can be considered opaque.
self.is_opaque = ctx.opaque_rect.contains_rect(&self.clipped_rect);
// Update tile transforms
let mut transform_spatial_nodes: Vec<SpatialNodeIndex> = self.transforms.drain().collect();
transform_spatial_nodes.sort();
for spatial_node_index in transform_spatial_nodes {
// Note: this is the only place where we don't know beforehand if the tile-affecting
// spatial node is below or above the current picture.
let transform = if ctx.cache_spatial_node_index >= spatial_node_index {
ctx.clip_scroll_tree
.get_relative_transform(
ctx.cache_spatial_node_index,
spatial_node_index,
)
} else {
ctx.clip_scroll_tree
.get_relative_transform(
spatial_node_index,
ctx.cache_spatial_node_index,
)
};
self.descriptor.transforms.push(transform.into());
}
// Content has changed if any images have changed.
// NOTE: This invalidation must be done after the request_resources
// calls for primitives during visibility update, or the
// is_image_dirty check may be incorrect.
for image_key in self.descriptor.image_keys.items() {
if state.resource_cache.is_image_dirty(*image_key) {
self.is_same_content = false;
break;
}
}
// Invalidate if the backing texture was evicted.
if state.resource_cache.texture_cache.is_allocated(&self.handle) {
// Request the backing texture so it won't get evicted this frame.
// We specifically want to mark the tile texture as used, even
// if it's detected not visible below and skipped. This is because
// we maintain the set of tiles we care about based on visibility
// during pre_update. If a tile still exists after that, we are
// assuming that it's either visible or we want to retain it for
// a while in case it gets scrolled back onto screen soon.
// TODO(gw): Consider switching to manual eviction policy?
state.resource_cache.texture_cache.request(&self.handle, state.gpu_cache);
} else {
// When a tile is invalidated, reset the opacity information
// so that it is recalculated during prim dependency updates.
self.is_valid = false;
}
// Invalidate the tile based on the content changing.
self.update_content_validity();
// If there are no primitives there is no need to draw or cache it.
if self.descriptor.prims.is_empty() {
return false;
}
if !self.world_rect.intersects(&ctx.global_screen_world_rect) {
return false;
}
// Decide how to handle this tile when drawing this frame.
if self.is_valid {
if ctx.debug_flags.contains(DebugFlags::PICTURE_CACHING_DBG) {
let tile_device_rect = self.world_rect * ctx.global_device_pixel_scale;
let label_offset = DeviceVector2D::new(20.0, 30.0);
let color = if self.is_opaque {
debug_colors::GREEN
} else {
debug_colors::YELLOW
};
state.scratch.push_debug_rect(
tile_device_rect,
color.scale_alpha(0.3),
);
if tile_device_rect.size.height >= label_offset.y {
state.scratch.push_debug_string(
tile_device_rect.origin + label_offset,
debug_colors::RED,
format!("{:?}: is_opaque={}", self.id, self.is_opaque),
);
}
}
} else {
if ctx.debug_flags.contains(DebugFlags::PICTURE_CACHING_DBG) {
state.scratch.push_debug_rect(
self.world_rect * ctx.global_device_pixel_scale,
debug_colors::RED,
);
}
// Ensure that this texture is allocated.
if !state.resource_cache.texture_cache.is_allocated(&self.handle) {
let tile_size = DeviceIntSize::new(
TILE_SIZE_WIDTH,
TILE_SIZE_HEIGHT,
);
state.resource_cache.texture_cache.update_picture_cache(
tile_size,
&mut self.handle,
state.gpu_cache,
);
}
self.visibility_mask = PrimitiveVisibilityMask::empty();
let dirty_region_index = state.dirty_region.dirty_rects.len();
// If we run out of dirty regions, then force the last dirty region to
// be a union of any remaining regions. This is an inefficiency, in that
// we'll add items to batches later on that are redundant / outside this
// tile, but it's really rare except in pathological cases (even on a
// 4k screen, the typical dirty region count is < 16).
if dirty_region_index < PrimitiveVisibilityMask::MAX_DIRTY_REGIONS {
self.visibility_mask.set_visible(dirty_region_index);
state.dirty_region.push(
self.world_rect,
self.visibility_mask,
);
} else {
self.visibility_mask.set_visible(PrimitiveVisibilityMask::MAX_DIRTY_REGIONS - 1);
state.dirty_region.include_rect(
PrimitiveVisibilityMask::MAX_DIRTY_REGIONS - 1,
self.world_rect,
);
}
}
true
}
} }
/// Defines a key that uniquely identifies a primitive instance. /// Defines a key that uniquely identifies a primitive instance.
@ -887,6 +1245,13 @@ impl TileCacheInstance {
FastHashMap::default(), FastHashMap::default(),
); );
let ctx = TilePreUpdateContext {
local_rect: self.local_rect,
pic_to_world_mapper,
fract_changed,
opacity_bindings: &self.opacity_bindings,
};
for y in y0 .. y1 { for y in y0 .. y1 {
for x in x0 .. x1 { for x in x0 .. x1 {
let key = TileOffset::new(x, y); let key = TileOffset::new(x, y);
@ -901,7 +1266,7 @@ impl TileCacheInstance {
// Ensure each tile is offset by the appropriate amount from the // Ensure each tile is offset by the appropriate amount from the
// origin, such that the content origin will be a whole number and // origin, such that the content origin will be a whole number and
// the snapping will be consistent. // the snapping will be consistent.
tile.rect = PictureRect::new( let rect = PictureRect::new(
PicturePoint::new( PicturePoint::new(
x as f32 * self.tile_size.width + fract_offset.x, x as f32 * self.tile_size.width + fract_offset.x,
y as f32 * self.tile_size.height + fract_offset.y, y as f32 * self.tile_size.height + fract_offset.y,
@ -909,13 +1274,10 @@ impl TileCacheInstance {
self.tile_size, self.tile_size,
); );
tile.clipped_rect = tile.rect tile.pre_update(
.intersection(&self.local_rect) rect,
.unwrap_or(PictureRect::zero()); &ctx,
);
tile.world_rect = pic_to_world_mapper
.map(&tile.rect)
.expect("bug: map local tile rect");
world_culling_rect = world_culling_rect.union(&tile.world_rect); world_culling_rect = world_culling_rect.union(&tile.world_rect);
@ -923,31 +1285,6 @@ impl TileCacheInstance {
} }
} }
// Do tile invalidation for any dependencies that we know now.
for (_, tile) in &mut self.tiles {
// Start frame assuming that the tile has the same content,
// unless the fractional offset of the transform root changed.
tile.is_same_content = !fract_changed;
// Content has changed if any opacity bindings changed.
for binding in tile.descriptor.opacity_bindings.items() {
if let OpacityBinding::Binding(id) = binding {
let changed = match self.opacity_bindings.get(id) {
Some(info) => info.changed,
None => true,
};
if changed {
tile.is_same_content = false;
break;
}
}
}
// Clear any dependencies so that when we rebuild them we
// can compare if the tile has the same content.
tile.clear();
}
world_culling_rect world_culling_rect
} }
@ -991,27 +1328,28 @@ impl TileCacheInstance {
return false; return false;
} }
// Build the list of resources that this primitive has dependencies on.
let mut opacity_bindings: SmallVec<[OpacityBinding; 4]> = SmallVec::new();
let mut clips: SmallVec<[ClipDescriptor; 8]> = SmallVec::new();
let mut image_keys: SmallVec<[ImageKey; 8]> = SmallVec::new();
let mut clip_spatial_nodes = FastHashSet::default();
let mut prim_clip_rect = PictureRect::zero();
// Some primitives can not be cached (e.g. external video images) // Some primitives can not be cached (e.g. external video images)
let is_cacheable = prim_instance.is_cacheable( let is_cacheable = prim_instance.is_cacheable(
&data_stores, &data_stores,
resource_cache, resource_cache,
); );
// Build the list of resources that this primitive has dependencies on.
let mut prim_info = PrimitiveDependencyInfo::new(
prim_instance.uid(),
prim_rect,
prim_instance.spatial_node_index,
is_cacheable,
);
// If there was a clip chain, add any clip dependencies to the list for this tile. // If there was a clip chain, add any clip dependencies to the list for this tile.
if let Some(prim_clip_chain) = prim_clip_chain { if let Some(prim_clip_chain) = prim_clip_chain {
prim_clip_rect = prim_clip_chain.pic_clip_rect; prim_info.prim_clip_rect = prim_clip_chain.pic_clip_rect;
let clip_instances = &clip_store let clip_instances = &clip_store
.clip_node_instances[prim_clip_chain.clips_range.to_range()]; .clip_node_instances[prim_clip_chain.clips_range.to_range()];
for clip_instance in clip_instances { for clip_instance in clip_instances {
clips.push(ClipDescriptor { prim_info.clips.push(ClipDescriptor {
uid: clip_instance.handle.uid(), uid: clip_instance.handle.uid(),
origin: clip_instance.local_pos.into(), origin: clip_instance.local_pos.into(),
}); });
@ -1019,7 +1357,7 @@ impl TileCacheInstance {
// If the clip has the same spatial node, the relative transform // If the clip has the same spatial node, the relative transform
// will always be the same, so there's no need to depend on it. // will always be the same, so there's no need to depend on it.
if clip_instance.spatial_node_index != self.spatial_node_index { if clip_instance.spatial_node_index != self.spatial_node_index {
clip_spatial_nodes.insert(clip_instance.spatial_node_index); prim_info.clip_spatial_nodes.insert(clip_instance.spatial_node_index);
} }
} }
} }
@ -1035,15 +1373,13 @@ impl TileCacheInstance {
// rect eventually means it doesn't affect that tile). // rect eventually means it doesn't affect that tile).
// TODO(gw): Get picture clips earlier (during the initial picture traversal // TODO(gw): Get picture clips earlier (during the initial picture traversal
// pass) so that we can calculate these correctly. // pass) so that we can calculate these correctly.
let clip_by_tile = match prim_instance.kind { match prim_instance.kind {
PrimitiveInstanceKind::Picture { pic_index,.. } => { PrimitiveInstanceKind::Picture { pic_index,.. } => {
// Pictures can depend on animated opacity bindings. // Pictures can depend on animated opacity bindings.
let pic = &pictures[pic_index.0]; let pic = &pictures[pic_index.0];
if let Some(PictureCompositeMode::Filter(Filter::Opacity(binding, _))) = pic.requested_composite_mode { if let Some(PictureCompositeMode::Filter(Filter::Opacity(binding, _))) = pic.requested_composite_mode {
opacity_bindings.push(binding.into()); prim_info.opacity_bindings.push(binding.into());
} }
false
} }
PrimitiveInstanceKind::Rectangle { data_handle, opacity_binding_index, .. } => { PrimitiveInstanceKind::Rectangle { data_handle, opacity_binding_index, .. } => {
if opacity_binding_index == OpacityBindingIndex::INVALID { if opacity_binding_index == OpacityBindingIndex::INVALID {
@ -1083,11 +1419,11 @@ impl TileCacheInstance {
} else { } else {
let opacity_binding = &opacity_binding_store[opacity_binding_index]; let opacity_binding = &opacity_binding_store[opacity_binding_index];
for binding in &opacity_binding.bindings { for binding in &opacity_binding.bindings {
opacity_bindings.push(OpacityBinding::from(*binding)); prim_info.opacity_bindings.push(OpacityBinding::from(*binding));
} }
} }
true prim_info.clip_by_tile = true;
} }
PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => { PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => {
let image_data = &data_stores.image[data_handle].kind; let image_data = &data_stores.image[data_handle].kind;
@ -1097,22 +1433,19 @@ impl TileCacheInstance {
if opacity_binding_index != OpacityBindingIndex::INVALID { if opacity_binding_index != OpacityBindingIndex::INVALID {
let opacity_binding = &opacity_binding_store[opacity_binding_index]; let opacity_binding = &opacity_binding_store[opacity_binding_index];
for binding in &opacity_binding.bindings { for binding in &opacity_binding.bindings {
opacity_bindings.push(OpacityBinding::from(*binding)); prim_info.opacity_bindings.push(OpacityBinding::from(*binding));
} }
} }
image_keys.push(image_data.key); prim_info.image_keys.push(image_data.key);
false
} }
PrimitiveInstanceKind::YuvImage { data_handle, .. } => { PrimitiveInstanceKind::YuvImage { data_handle, .. } => {
let yuv_image_data = &data_stores.yuv_image[data_handle].kind; let yuv_image_data = &data_stores.yuv_image[data_handle].kind;
image_keys.extend_from_slice(&yuv_image_data.yuv_key); prim_info.image_keys.extend_from_slice(&yuv_image_data.yuv_key);
false
} }
PrimitiveInstanceKind::ImageBorder { data_handle, .. } => { PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
let border_data = &data_stores.image_border[data_handle].kind; let border_data = &data_stores.image_border[data_handle].kind;
image_keys.push(border_data.request.key); prim_info.image_keys.push(border_data.request.key);
false
} }
PrimitiveInstanceKind::PushClipChain | PrimitiveInstanceKind::PushClipChain |
PrimitiveInstanceKind::PopClipChain => { PrimitiveInstanceKind::PopClipChain => {
@ -1138,13 +1471,11 @@ impl TileCacheInstance {
}; };
if on_picture_surface && subpx_requested { if on_picture_surface && subpx_requested {
if !self.opaque_rect.contains_rect(&prim_clip_rect) { if !self.opaque_rect.contains_rect(&prim_info.prim_clip_rect) {
self.subpixel_mode = SubpixelMode::Deny; self.subpixel_mode = SubpixelMode::Deny;
} }
} }
} }
false
} }
PrimitiveInstanceKind::LineDecoration { .. } | PrimitiveInstanceKind::LineDecoration { .. } |
PrimitiveInstanceKind::Clear { .. } | PrimitiveInstanceKind::Clear { .. } |
@ -1152,7 +1483,6 @@ impl TileCacheInstance {
PrimitiveInstanceKind::LinearGradient { .. } | PrimitiveInstanceKind::LinearGradient { .. } |
PrimitiveInstanceKind::RadialGradient { .. } => { PrimitiveInstanceKind::RadialGradient { .. } => {
// These don't contribute dependencies // These don't contribute dependencies
false
} }
}; };
@ -1164,68 +1494,10 @@ impl TileCacheInstance {
let key = TileOffset::new(x, y); let key = TileOffset::new(x, y);
let tile = self.tiles.get_mut(&key).expect("bug: no tile"); let tile = self.tiles.get_mut(&key).expect("bug: no tile");
// Mark if the tile is cacheable at all. tile.add_prim_dependency(
tile.is_same_content &= is_cacheable; &prim_info,
self.spatial_node_index,
// Include any image keys this tile depends on. );
tile.descriptor.image_keys.extend_from_slice(&image_keys);
// // Include any opacity bindings this primitive depends on.
tile.descriptor.opacity_bindings.extend_from_slice(&opacity_bindings);
// TODO(gw): The origin of background rects produced by APZ changes
// in Gecko during scrolling. Consider investigating this so the
// hack / workaround below is not required.
let (prim_origin, prim_clip_rect) = if clip_by_tile {
let tile_p0 = tile.clipped_rect.origin;
let tile_p1 = tile.clipped_rect.bottom_right();
let clip_p0 = PicturePoint::new(
clampf(prim_clip_rect.origin.x, tile_p0.x, tile_p1.x),
clampf(prim_clip_rect.origin.y, tile_p0.y, tile_p1.y),
);
let clip_p1 = PicturePoint::new(
clampf(prim_clip_rect.origin.x + prim_clip_rect.size.width, tile_p0.x, tile_p1.x),
clampf(prim_clip_rect.origin.y + prim_clip_rect.size.height, tile_p0.y, tile_p1.y),
);
(
PicturePoint::new(
clampf(prim_rect.origin.x, tile_p0.x, tile_p1.x),
clampf(prim_rect.origin.y, tile_p0.y, tile_p1.y),
),
PictureRect::new(
clip_p0,
PictureSize::new(
clip_p1.x - clip_p0.x,
clip_p1.y - clip_p0.y,
),
),
)
} else {
(prim_rect.origin, prim_clip_rect)
};
// Update the tile descriptor, used for tile comparison during scene swaps.
tile.descriptor.prims.push(PrimitiveDescriptor {
prim_uid: prim_instance.uid(),
origin: prim_origin.into(),
first_clip: tile.descriptor.clips.len() as u16,
clip_count: clips.len() as u16,
prim_clip_rect: prim_clip_rect.into(),
});
tile.descriptor.clips.extend_from_slice(&clips);
// If the primitive has the same spatial node, the relative transform
// will always be the same, so there's no need to depend on it.
if prim_instance.spatial_node_index != self.spatial_node_index {
tile.transforms.insert(prim_instance.spatial_node_index);
}
for spatial_node_index in &clip_spatial_nodes {
tile.transforms.insert(*spatial_node_index);
}
} }
} }
@ -1244,144 +1516,30 @@ impl TileCacheInstance {
) { ) {
self.tiles_to_draw.clear(); self.tiles_to_draw.clear();
self.dirty_region.clear(); self.dirty_region.clear();
let mut dirty_region_index = 0;
let ctx = TilePostUpdateContext {
debug_flags: frame_context.debug_flags,
global_device_pixel_scale: frame_context.global_device_pixel_scale,
global_screen_world_rect: frame_context.global_screen_world_rect,
opaque_rect: self.opaque_rect,
cache_spatial_node_index: self.spatial_node_index,
clip_scroll_tree: frame_context.clip_scroll_tree,
};
let mut state = TilePostUpdateState {
resource_cache,
gpu_cache,
scratch,
dirty_region: &mut self.dirty_region,
};
// Step through each tile and invalidate if the dependencies have changed. // Step through each tile and invalidate if the dependencies have changed.
for (key, tile) in self.tiles.iter_mut() { for (key, tile) in self.tiles.iter_mut() {
// Check if this tile can be considered opaque. if tile.post_update(
tile.is_opaque = self.opaque_rect.contains_rect(&tile.clipped_rect); &ctx,
&mut state,
// Update tile transforms ) {
let mut transform_spatial_nodes: Vec<SpatialNodeIndex> = tile.transforms.drain().collect(); self.tiles_to_draw.push(*key);
transform_spatial_nodes.sort();
for spatial_node_index in transform_spatial_nodes {
// Note: this is the only place where we don't know beforehand if the tile-affecting
// spatial node is below or above the current picture.
let transform = if self.spatial_node_index >= spatial_node_index {
frame_context.clip_scroll_tree
.get_relative_transform(
self.spatial_node_index,
spatial_node_index,
)
} else {
frame_context.clip_scroll_tree
.get_relative_transform(
spatial_node_index,
self.spatial_node_index,
)
};
tile.descriptor.transforms.push(transform.into());
}
// Content has changed if any images have changed.
// NOTE: This invalidation must be done after the request_resources
// calls for primitives during visibility update, or the
// is_image_dirty check may be incorrect.
for image_key in tile.descriptor.image_keys.items() {
if resource_cache.is_image_dirty(*image_key) {
tile.is_same_content = false;
break;
}
}
// Invalidate if the backing texture was evicted.
if resource_cache.texture_cache.is_allocated(&tile.handle) {
// Request the backing texture so it won't get evicted this frame.
// We specifically want to mark the tile texture as used, even
// if it's detected not visible below and skipped. This is because
// we maintain the set of tiles we care about based on visibility
// during pre_update. If a tile still exists after that, we are
// assuming that it's either visible or we want to retain it for
// a while in case it gets scrolled back onto screen soon.
// TODO(gw): Consider switching to manual eviction policy?
resource_cache.texture_cache.request(&tile.handle, gpu_cache);
} else {
// When a tile is invalidated, reset the opacity information
// so that it is recalculated during prim dependency updates.
tile.is_valid = false;
}
// Invalidate the tile based on the content changing.
tile.update_content_validity();
// If there are no primitives there is no need to draw or cache it.
if tile.descriptor.prims.is_empty() {
continue;
}
if !tile.world_rect.intersects(&frame_context.global_screen_world_rect) {
continue;
}
self.tiles_to_draw.push(*key);
// Decide how to handle this tile when drawing this frame.
if tile.is_valid {
if frame_context.debug_flags.contains(DebugFlags::PICTURE_CACHING_DBG) {
let tile_device_rect = tile.world_rect * frame_context.global_device_pixel_scale;
let label_offset = DeviceVector2D::new(20.0, 30.0);
let color = if tile.is_opaque {
debug_colors::GREEN
} else {
debug_colors::YELLOW
};
scratch.push_debug_rect(
tile_device_rect,
color.scale_alpha(0.3),
);
if tile_device_rect.size.height >= label_offset.y {
scratch.push_debug_string(
tile_device_rect.origin + label_offset,
debug_colors::RED,
format!("{:?}: is_opaque={}", tile.id, tile.is_opaque),
);
}
}
} else {
if frame_context.debug_flags.contains(DebugFlags::PICTURE_CACHING_DBG) {
scratch.push_debug_rect(
tile.world_rect * frame_context.global_device_pixel_scale,
debug_colors::RED,
);
}
// Ensure that this texture is allocated.
if !resource_cache.texture_cache.is_allocated(&tile.handle) {
let tile_size = DeviceIntSize::new(
TILE_SIZE_WIDTH,
TILE_SIZE_HEIGHT,
);
resource_cache.texture_cache.update_picture_cache(
tile_size,
&mut tile.handle,
gpu_cache,
);
}
tile.visibility_mask = PrimitiveVisibilityMask::empty();
// If we run out of dirty regions, then force the last dirty region to
// be a union of any remaining regions. This is an inefficiency, in that
// we'll add items to batches later on that are redundant / outside this
// tile, but it's really rare except in pathological cases (even on a
// 4k screen, the typical dirty region count is < 16).
if dirty_region_index < PrimitiveVisibilityMask::MAX_DIRTY_REGIONS {
tile.visibility_mask.set_visible(dirty_region_index);
self.dirty_region.push(
tile.world_rect,
tile.visibility_mask,
);
dirty_region_index += 1;
} else {
tile.visibility_mask.set_visible(PrimitiveVisibilityMask::MAX_DIRTY_REGIONS - 1);
self.dirty_region.include_rect(
PrimitiveVisibilityMask::MAX_DIRTY_REGIONS - 1,
tile.world_rect,
);
}
} }
} }