Bug 1642308 - Fix some snapping related picture cache tile rect calculations. r=gw

Our fract offset for the tiles should be a simple mapping of the snapped
device position back to picture space. If no snapping is required, then
the position will be precisely the origin. When using this value to
decide if the position has changed for glyph subpixel offset purposes,
we must consider it in device space, since picture to device space can
be effectively arbitrary.

We update the stored fract offset at which a tile was rendered whenever
we invalidate the whole tile, not just when we detect the fract offset
has changed by a notable amount. This should reduce spurious
invalidations since the tile was actually rendered at a different offset
that we had recorded prior to this patch.

Also, when evaluating the tile's valid rect, we cannot use the local
valid rect. The device valid rect we use is the local mapped from
picture space, but also snapped. Thus it makes far more sense to compare
that which we used for drawing purposes which has the bonus of avoiding
floating point errors.

Differential Revision: https://phabricator.services.mozilla.com/D91156
This commit is contained in:
Andrew Osmond 2020-10-01 04:18:54 +00:00
parent 0eb1b28da9
commit 45dd1ce875

View File

@ -521,6 +521,7 @@ struct TilePreUpdateContext {
/// The fractional position of the picture cache, which may
/// require invalidation of all tiles.
fract_offset: PictureVector2D,
device_fract_offset: DeviceVector2D,
/// The optional background color of the picture cache instance
background_color: Option<ColorF>,
@ -824,8 +825,8 @@ pub enum PrimitiveCompareResultDetail {
pub enum InvalidationReason {
/// The fractional offset changed
FractionalOffset {
old: PictureVector2D,
new: PictureVector2D,
old: DeviceVector2D,
new: DeviceVector2D,
},
/// The background color changed
BackgroundColor {
@ -863,7 +864,7 @@ pub enum InvalidationReason {
pub struct TileSerializer {
pub rect: PictureRect,
pub current_descriptor: TileDescriptor,
pub fract_offset: PictureVector2D,
pub device_fract_offset: DeviceVector2D,
pub id: TileId,
pub root: TileNode,
pub background_color: Option<ColorF>,
@ -898,6 +899,9 @@ pub struct Tile {
pub device_dirty_rect: DeviceRect,
/// Device space rect that contains valid pixels region of this tile.
pub device_valid_rect: DeviceRect,
/// Device space rect that contains valid pixels region of this tile
/// from the previous frame.
pub prev_device_valid_rect: DeviceRect,
/// Uniquely describes the content of this tile, in a way that can be
/// (reasonably) efficiently hashed and compared.
pub current_descriptor: TileDescriptor,
@ -915,7 +919,7 @@ pub struct Tile {
/// The current fractional offset of the cache transform root. If this changes,
/// all tiles need to be invalidated and redrawn, since snapping differences are
/// likely to occur.
fract_offset: PictureVector2D,
device_fract_offset: DeviceVector2D,
/// The tile id is stable between display lists and / or frames,
/// if the tile is retained. Useful for debugging tile evictions.
pub id: TileId,
@ -954,6 +958,7 @@ impl Tile {
local_tile_box: PictureBox2D::zero(),
world_tile_rect: WorldRect::zero(),
device_valid_rect: DeviceRect::zero(),
prev_device_valid_rect: DeviceRect::zero(),
local_dirty_rect: PictureRect::zero(),
device_dirty_rect: DeviceRect::zero(),
surface: None,
@ -961,7 +966,7 @@ impl Tile {
prev_descriptor: TileDescriptor::new(),
is_valid: false,
is_visible: false,
fract_offset: PictureVector2D::zero(),
device_fract_offset: DeviceVector2D::zero(),
id,
is_opaque: false,
root: TileNode::new_leaf(Vec::new()),
@ -979,7 +984,7 @@ impl Tile {
fn print(&self, pt: &mut dyn PrintTreePrinter) {
pt.new_level(format!("Tile {:?}", self.id));
pt.add_item(format!("local_tile_rect: {:?}", self.local_tile_rect));
pt.add_item(format!("fract_offset: {:?}", self.fract_offset));
pt.add_item(format!("device_fract_offset: {:?}", self.device_fract_offset));
pt.add_item(format!("background_color: {:?}", self.background_color));
pt.add_item(format!("invalidation_reason: {:?}", self.invalidation_reason));
self.current_descriptor.print(pt);
@ -1045,7 +1050,7 @@ impl Tile {
}
// TODO(gw): We can avoid invalidating the whole tile in some cases here,
// but it should be a fairly rare invalidation case.
if self.current_descriptor.local_valid_rect != self.prev_descriptor.local_valid_rect {
if self.device_valid_rect != self.prev_device_valid_rect {
self.invalidate(None, InvalidationReason::ValidRectChanged);
state.composite_state.dirty_rects_are_valid = false;
}
@ -1113,15 +1118,16 @@ impl Tile {
return;
}
// Determine if the fractional offset of the transform is different this frame
// from the currently cached tile set.
let fract_changed = (self.fract_offset.x - ctx.fract_offset.x).abs() > 0.01 ||
(self.fract_offset.y - ctx.fract_offset.y).abs() > 0.01;
// We may need to rerender if glyph subpixel positions have changed. Note
// that we update the tile fract offset itself after we have completed
// invalidation. This allows for other whole tile invalidation cases to
// update the fract offset appropriately.
let fract_delta = self.device_fract_offset - ctx.device_fract_offset;
let fract_changed = fract_delta.x.abs() > 0.01 || fract_delta.y.abs() > 0.01;
if fract_changed {
self.invalidate(None, InvalidationReason::FractionalOffset {
old: self.fract_offset,
new: ctx.fract_offset });
self.fract_offset = ctx.fract_offset;
old: self.device_fract_offset,
new: ctx.device_fract_offset });
}
if ctx.background_color != self.background_color {
@ -1137,6 +1143,7 @@ impl Tile {
&mut self.current_descriptor,
&mut self.prev_descriptor,
);
self.prev_device_valid_rect = self.device_valid_rect;
self.current_descriptor.clear();
self.root.clear(self.local_tile_rect.to_box2d());
@ -2324,6 +2331,8 @@ pub struct TileCacheInstance {
frames_until_size_eval: usize,
/// The current fractional offset of the cached picture
fract_offset: PictureVector2D,
/// The current device fractional offset of the cached picture
device_fract_offset: DeviceVector2D,
/// For DirectComposition, virtual surfaces don't support negative coordinates. However,
/// picture cache tile coordinates can be negative. To handle this, we apply an offset
/// to each tile in DirectComposition. We want to change this as little as possible,
@ -2399,6 +2408,7 @@ impl TileCacheInstance {
current_tile_size: DeviceIntSize::zero(),
frames_until_size_eval: 0,
fract_offset: PictureVector2D::zero(),
device_fract_offset: DeviceVector2D::zero(),
// Default to centering the virtual offset in the middle of the DC virtual surface
virtual_offset: DeviceIntPoint::new(
params.virtual_surface_size / 2,
@ -2648,6 +2658,7 @@ impl TileCacheInstance {
let device_origin = world_origin * frame_context.global_device_pixel_scale;
let desired_device_origin = device_origin.round();
self.device_position = desired_device_origin;
self.device_fract_offset = desired_device_origin - device_origin;
// Unmap from device space to world space rect
let ref_world_rect = WorldRect::new(
@ -2655,17 +2666,13 @@ impl TileCacheInstance {
WorldSize::new(1.0, 1.0),
);
// Unmap from world space to picture space
let ref_point = pic_to_world_mapper
// Unmap from world space to picture space; this should be the fractional offset
// required in picture space to align in device space
self.fract_offset = pic_to_world_mapper
.unmap(&ref_world_rect)
.expect("bug: unable to unmap ref world rect")
.origin;
// Extract the fractional offset required in picture space to align in device space
self.fract_offset = PictureVector2D::new(
ref_point.x.fract(),
ref_point.y.fract(),
);
.origin
.to_vector();
// Do a hacky diff of opacity binding values from the last frame. This is
// used later on during tile invalidation tests.
@ -2841,6 +2848,7 @@ impl TileCacheInstance {
let ctx = TilePreUpdateContext {
pic_to_world_mapper,
fract_offset: self.fract_offset,
device_fract_offset: self.device_fract_offset,
background_color: self.background_color,
global_screen_world_rect: frame_context.global_screen_world_rect,
tile_size: self.tile_size,
@ -5442,6 +5450,12 @@ impl PicturePrimitive {
}
}
// If the entire tile valid region is dirty, we can update the fract offset
// at which the tile was rendered.
if tile.device_dirty_rect.contains_rect(&tile.device_valid_rect) {
tile.device_fract_offset = tile_cache.device_fract_offset;
}
// Now that the tile is valid, reset the dirty rect.
tile.local_dirty_rect = PictureRect::zero();
tile.is_valid = true;
@ -5567,7 +5581,7 @@ impl PicturePrimitive {
tile_cache_tiny.tiles.insert(*key, TileSerializer {
rect: tile.local_tile_rect,
current_descriptor: tile.current_descriptor.clone(),
fract_offset: tile.fract_offset,
device_fract_offset: tile.device_fract_offset,
id: tile.id,
root: tile.root.clone(),
background_color: tile.background_color,