Bug 1594305 - Only allocate compositor surfaces for tiles that are not occluded r=nical

Tiles that are occluded are generally never seen, or only seen
occasionally. To reduce the number of compositor surfaces:

 * Defer native surface allocation until after occlusion culling occurs.
 * If a tile has a native surface, then becomes occluded, drop the surface.

With this scheme, the number of unused native surfaces will always be 0
on a page that doesn't have scrolling. For a page that has a scrollable
region, there will be a small number of unused tiles retained. The unused
tiles are those that are (a) not occluded (b) not currently visible (c) are
in the display port. We retain these for a small amount of time in case
they get scrolled back on screen.

This makes the allocation patterns for native surfaces match the way
that picture cache surfaces are allocated for simple compositing mode.

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Glenn Watson 2019-11-06 19:05:43 +00:00
parent 8b3908b9e4
commit d97c81e852
2 changed files with 85 additions and 51 deletions

View File

@ -5,14 +5,18 @@
use api::ColorF;
use api::units::{DeviceRect, DeviceIntSize, DeviceIntRect, DeviceIntPoint, WorldRect, DevicePixelScale};
use crate::gpu_types::{ZBufferId, ZBufferIdGenerator};
use crate::picture::{ResolvedSurfaceTexture, SurfaceTextureDescriptor};
use crate::picture::{ResolvedSurfaceTexture};
use std::{ops, u64};
use std::sync::atomic::{AtomicU64, Ordering};
/*
Types and definitions related to compositing picture cache tiles
and/or OS compositor integration.
*/
// Counter for generating unique native surface ids
static NEXT_NATIVE_SURFACE_ID: AtomicU64 = AtomicU64::new(0);
/// Describes details of an operation to apply to a native surface
#[derive(Debug, Clone)]
#[cfg_attr(feature = "capture", derive(Serialize))]
@ -192,10 +196,11 @@ impl CompositeState {
/// specified id and dimensions.
pub fn create_surface(
&mut self,
id: NativeSurfaceId,
size: DeviceIntSize,
is_opaque: bool,
) -> SurfaceTextureDescriptor {
) -> NativeSurfaceId {
let id = NativeSurfaceId(NEXT_NATIVE_SURFACE_ID.fetch_add(1, Ordering::Relaxed));
self.native_surface_updates.push(
NativeSurfaceOperation {
id,
@ -206,10 +211,7 @@ impl CompositeState {
}
);
SurfaceTextureDescriptor::NativeSurface {
id,
size,
}
id
}
/// Queue up destruction of an existing native OS surface. This is used when

View File

@ -440,7 +440,7 @@ pub enum SurfaceTextureDescriptor {
/// surface identified by arbitrary id.
NativeSurface {
/// The arbitrary id of this surface.
id: NativeSurfaceId,
id: Option<NativeSurfaceId>,
/// Size in device pixels of the native surface.
size: DeviceIntSize,
},
@ -484,7 +484,7 @@ impl SurfaceTextureDescriptor {
}
SurfaceTextureDescriptor::NativeSurface { id, size } => {
ResolvedSurfaceTexture::NativeSurface {
id: *id,
id: id.expect("bug: native surface not allocated"),
size: *size,
}
}
@ -871,7 +871,7 @@ impl Tile {
// the tile was previously a color, or not set, then just set
// up a new texture cache handle.
match self.surface.take() {
Some(TileSurface::Texture { descriptor, visibility_mask }) => {
Some(TileSurface::Texture { mut descriptor, visibility_mask }) => {
// If opacity changed, and this is a native OS compositor surface,
// it needs to be recreated.
// TODO(gw): This is a limitation of the DirectComposite APIs. It might
@ -879,18 +879,17 @@ impl Tile {
// a property on a surface, if we ever see pages where this
// is changing frequently.
if opacity_changed {
if let SurfaceTextureDescriptor::NativeSurface { id, size } = descriptor {
if let SurfaceTextureDescriptor::NativeSurface { ref mut id, .. } = descriptor {
// Reset the dirty rect and tile validity in this case, to
// force the new tile to be completely redrawn.
self.dirty_rect = self.rect;
self.is_valid = false;
state.composite_state.destroy_surface(id);
state.composite_state.create_surface(
id,
size,
self.is_opaque,
);
// If this tile has a currently allocated native surface, destroy it. It
// will be re-allocated next time it's determined to be visible.
if let Some(id) = id.take() {
state.composite_state.destroy_surface(id);
}
}
}
@ -914,13 +913,13 @@ impl Tile {
}
}
CompositorKind::Native { .. } => {
// For a new native OS surface, we need to queue up creation
// of a native surface to be passed to the compositor interface.
state.composite_state.create_surface(
NativeSurfaceId(self.id.0 as u64),
ctx.current_tile_size,
self.is_opaque,
)
// Create a native surface surface descriptor, but don't allocate
// a surface yet. The surface is allocated *after* occlusion
// culling occurs, so that only visible tiles allocate GPU memory.
SurfaceTextureDescriptor::NativeSurface {
id: None,
size: ctx.current_tile_size,
}
}
};
@ -3328,11 +3327,23 @@ impl PicturePrimitive {
}
};
let surface = tile.surface.as_mut().expect("no tile surface set!");
// If that draw rect is occluded by some set of tiles in front of it,
// then mark it as not visible and skip drawing. When it's not occluded
// it will fail this test, and get rasterized by the render task setup
// code below.
if frame_state.composite_state.is_tile_occluded(tile_cache.slice, tile_draw_rect) {
// If this tile has an allocated native surface, free it, since it's completely
// occluded. We will need to re-allocate this surface if it becomes visible,
// but that's likely to be rare (e.g. when there is no content display list
// for a frame or two during a tab switch).
if let TileSurface::Texture { descriptor: SurfaceTextureDescriptor::NativeSurface { id, .. }, .. } = surface {
if let Some(id) = id.take() {
frame_state.composite_state.destroy_surface(id);
}
}
tile.is_visible = false;
continue;
}
@ -3347,8 +3358,6 @@ impl PicturePrimitive {
frame_state.resource_cache.set_image_active(*image_key);
}
let surface = tile.surface.as_mut().expect("no tile surface set!");
if frame_context.debug_flags.contains(DebugFlags::PICTURE_CACHING_DBG) {
tile.root.draw_debug_rects(
&map_pic_to_world,
@ -3373,23 +3382,34 @@ impl PicturePrimitive {
}
}
if let TileSurface::Texture { descriptor: SurfaceTextureDescriptor::TextureCache { ref handle, .. }, .. } = surface {
// Invalidate if the backing texture was evicted.
if frame_state.resource_cache.texture_cache.is_allocated(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?
frame_state.resource_cache.texture_cache.request(handle, frame_state.gpu_cache);
} else {
// If the texture was evicted on a previous frame, we need to assume
// that the entire tile rect is dirty.
tile.is_valid = false;
tile.dirty_rect = tile.rect;
if let TileSurface::Texture { descriptor, .. } = surface {
match descriptor {
SurfaceTextureDescriptor::TextureCache { ref handle, .. } => {
// Invalidate if the backing texture was evicted.
if frame_state.resource_cache.texture_cache.is_allocated(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?
frame_state.resource_cache.texture_cache.request(handle, frame_state.gpu_cache);
} else {
// If the texture was evicted on a previous frame, we need to assume
// that the entire tile rect is dirty.
tile.is_valid = false;
tile.dirty_rect = tile.rect;
}
}
SurfaceTextureDescriptor::NativeSurface { id, .. } => {
if id.is_none() {
// There is no current surface allocation, so ensure the entire tile is invalidated
tile.is_valid = false;
tile.dirty_rect = tile.rect;
}
}
}
}
@ -3402,13 +3422,23 @@ impl PicturePrimitive {
// Ensure that this texture is allocated.
if let TileSurface::Texture { ref mut descriptor, ref mut visibility_mask } = surface {
if let SurfaceTextureDescriptor::TextureCache { ref mut handle } = descriptor {
if !frame_state.resource_cache.texture_cache.is_allocated(handle) {
frame_state.resource_cache.texture_cache.update_picture_cache(
tile_cache.current_tile_size,
handle,
frame_state.gpu_cache,
);
match descriptor {
SurfaceTextureDescriptor::TextureCache { ref mut handle } => {
if !frame_state.resource_cache.texture_cache.is_allocated(handle) {
frame_state.resource_cache.texture_cache.update_picture_cache(
tile_cache.current_tile_size,
handle,
frame_state.gpu_cache,
);
}
}
SurfaceTextureDescriptor::NativeSurface { id, size } => {
if id.is_none() {
*id = Some(frame_state.composite_state.create_surface(
*size,
tile.is_opaque,
));
}
}
}
@ -4871,7 +4901,9 @@ impl CompositeState {
// possible for display port tiles to be created that never
// come on screen, and thus never get a native surface allocated.
if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::NativeSurface { id, .. }, .. }) = tile.surface {
self.destroy_surface(id);
if let Some(id) = id {
self.destroy_surface(id);
}
}
}
}