зеркало из 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.
|
/// 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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче