зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
c338fe4e07
Коммит
1dff77fb01
|
@ -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.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
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
|
||||
/// must be called even if the tile is not currently
|
||||
/// 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_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.
|
||||
|
@ -887,6 +1245,13 @@ impl TileCacheInstance {
|
|||
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 x in x0 .. x1 {
|
||||
let key = TileOffset::new(x, y);
|
||||
|
@ -901,7 +1266,7 @@ impl TileCacheInstance {
|
|||
// Ensure each tile is offset by the appropriate amount from the
|
||||
// origin, such that the content origin will be a whole number and
|
||||
// the snapping will be consistent.
|
||||
tile.rect = PictureRect::new(
|
||||
let rect = PictureRect::new(
|
||||
PicturePoint::new(
|
||||
x as f32 * self.tile_size.width + fract_offset.x,
|
||||
y as f32 * self.tile_size.height + fract_offset.y,
|
||||
|
@ -909,13 +1274,10 @@ impl TileCacheInstance {
|
|||
self.tile_size,
|
||||
);
|
||||
|
||||
tile.clipped_rect = tile.rect
|
||||
.intersection(&self.local_rect)
|
||||
.unwrap_or(PictureRect::zero());
|
||||
|
||||
tile.world_rect = pic_to_world_mapper
|
||||
.map(&tile.rect)
|
||||
.expect("bug: map local tile rect");
|
||||
tile.pre_update(
|
||||
rect,
|
||||
&ctx,
|
||||
);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -991,27 +1328,28 @@ impl TileCacheInstance {
|
|||
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)
|
||||
let is_cacheable = prim_instance.is_cacheable(
|
||||
&data_stores,
|
||||
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 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
|
||||
.clip_node_instances[prim_clip_chain.clips_range.to_range()];
|
||||
for clip_instance in clip_instances {
|
||||
clips.push(ClipDescriptor {
|
||||
prim_info.clips.push(ClipDescriptor {
|
||||
uid: clip_instance.handle.uid(),
|
||||
origin: clip_instance.local_pos.into(),
|
||||
});
|
||||
|
@ -1019,7 +1357,7 @@ impl TileCacheInstance {
|
|||
// 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.
|
||||
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).
|
||||
// TODO(gw): Get picture clips earlier (during the initial picture traversal
|
||||
// pass) so that we can calculate these correctly.
|
||||
let clip_by_tile = match prim_instance.kind {
|
||||
match prim_instance.kind {
|
||||
PrimitiveInstanceKind::Picture { pic_index,.. } => {
|
||||
// Pictures can depend on animated opacity bindings.
|
||||
let pic = &pictures[pic_index.0];
|
||||
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, .. } => {
|
||||
if opacity_binding_index == OpacityBindingIndex::INVALID {
|
||||
|
@ -1083,11 +1419,11 @@ impl TileCacheInstance {
|
|||
} else {
|
||||
let opacity_binding = &opacity_binding_store[opacity_binding_index];
|
||||
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, .. } => {
|
||||
let image_data = &data_stores.image[data_handle].kind;
|
||||
|
@ -1097,22 +1433,19 @@ impl TileCacheInstance {
|
|||
if opacity_binding_index != OpacityBindingIndex::INVALID {
|
||||
let opacity_binding = &opacity_binding_store[opacity_binding_index];
|
||||
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);
|
||||
false
|
||||
prim_info.image_keys.push(image_data.key);
|
||||
}
|
||||
PrimitiveInstanceKind::YuvImage { data_handle, .. } => {
|
||||
let yuv_image_data = &data_stores.yuv_image[data_handle].kind;
|
||||
image_keys.extend_from_slice(&yuv_image_data.yuv_key);
|
||||
false
|
||||
prim_info.image_keys.extend_from_slice(&yuv_image_data.yuv_key);
|
||||
}
|
||||
PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
|
||||
let border_data = &data_stores.image_border[data_handle].kind;
|
||||
image_keys.push(border_data.request.key);
|
||||
false
|
||||
prim_info.image_keys.push(border_data.request.key);
|
||||
}
|
||||
PrimitiveInstanceKind::PushClipChain |
|
||||
PrimitiveInstanceKind::PopClipChain => {
|
||||
|
@ -1138,13 +1471,11 @@ impl TileCacheInstance {
|
|||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
PrimitiveInstanceKind::LineDecoration { .. } |
|
||||
PrimitiveInstanceKind::Clear { .. } |
|
||||
|
@ -1152,7 +1483,6 @@ impl TileCacheInstance {
|
|||
PrimitiveInstanceKind::LinearGradient { .. } |
|
||||
PrimitiveInstanceKind::RadialGradient { .. } => {
|
||||
// These don't contribute dependencies
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1164,68 +1494,10 @@ impl TileCacheInstance {
|
|||
let key = TileOffset::new(x, y);
|
||||
let tile = self.tiles.get_mut(&key).expect("bug: no tile");
|
||||
|
||||
// Mark if the tile is cacheable at all.
|
||||
tile.is_same_content &= is_cacheable;
|
||||
|
||||
// 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),
|
||||
tile.add_prim_dependency(
|
||||
&prim_info,
|
||||
self.spatial_node_index,
|
||||
);
|
||||
|
||||
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.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.
|
||||
for (key, tile) in self.tiles.iter_mut() {
|
||||
// Check if this tile can be considered opaque.
|
||||
tile.is_opaque = self.opaque_rect.contains_rect(&tile.clipped_rect);
|
||||
|
||||
// Update tile transforms
|
||||
let mut transform_spatial_nodes: Vec<SpatialNodeIndex> = tile.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 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;
|
||||
}
|
||||
|
||||
if tile.post_update(
|
||||
&ctx,
|
||||
&mut state,
|
||||
) {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче