Bug 1704956 - Support non-opaque compositor surfaces. r=gfx-reviewers,lsalzman

This patch enables the sub-slice support landed in previous
patches, to allow non-opaque external images to be treated as
native compositor surfaces.

Each picture cache has one or more sub-slices, which form the
tile grid of picture cache targets. Sub-slices are added as
required based on the number of compositor surfaces that are
found during scene building.

As primitives are added to the picture cache dependencies, the
appropriate sub-slice to add the primitive to is calculated. This
depends on whether the primitive intersects any of the compositor
surface rects (and thus must be drawn after that surface). Prims
are added to the earliest sub-slice possible, to maximize the
chance that the primitive is on the primary sub-slice. This means
the prim can be rendered with subpixel AA due to an opaque background
in most cases, and also reduces the number of alpha tiles that are
allocated and must be composited.

Creating extra sub-slices and tile grids is relatively cheap - the
backing surface for a tile is only allocated once there are prims
in that tile to draw. The valid rect tracked for each tile also
ensures that a minimum rect is used during drawing and compositing.

Differential Revision: https://phabricator.services.mozilla.com/D111967
This commit is contained in:
Glenn Watson 2021-04-14 09:34:50 +00:00
Родитель 988bc29033
Коммит 177be1a3be
7 изменённых файлов: 299 добавлений и 379 удалений

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

@ -29,7 +29,7 @@ use crate::renderer::{BlendMode, ShaderColorMode};
use crate::renderer::MAX_VERTEX_TEXTURE_WIDTH;
use crate::resource_cache::{GlyphFetchResult, ImageProperties, ImageRequest, ResourceCache};
use crate::space::SpaceMapper;
use crate::visibility::{PrimitiveVisibility, PrimitiveVisibilityFlags, VisibilityState};
use crate::visibility::{PrimitiveVisibilityFlags, VisibilityState};
use smallvec::SmallVec;
use std::{f32, i32, usize};
use crate::util::{project_rect, MaxRect, TransformedRectKind};
@ -889,73 +889,6 @@ impl BatchBuilder {
}
}
// If an image is being drawn as a compositor surface, we don't want
// to draw the surface itself into the tile. Instead, we draw a transparent
// rectangle that writes to the z-buffer where this compositor surface is.
// That ensures we 'cut out' the part of the tile that has the compositor
// surface on it, allowing us to draw this tile as an overlay on top of
// the compositor surface.
// TODO(gw): There's a slight performance cost to doing this cutout rectangle
// if we end up not needing to use overlay mode. Consider skipping
// the cutout completely in this path.
fn emit_placeholder(
&mut self,
prim_rect: LayoutRect,
prim_info: &PrimitiveVisibility,
batch_filter: &BatchFilter,
z_id: ZBufferId,
transform_id: TransformPaletteId,
batch_features: BatchFeatures,
ctx: &RenderTargetContext,
gpu_cache: &mut GpuCache,
prim_headers: &mut PrimitiveHeaders,
render_tasks: &RenderTaskGraph,
) {
let batch_params = BrushBatchParameters::shared(
BrushBatchKind::Solid,
TextureSet::UNTEXTURED,
[get_shader_opacity(0.0), 0, 0, 0],
0,
);
let prim_cache_address = gpu_cache.get_address(
&ctx.globals.default_transparent_rect_handle,
);
let prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: prim_info.combined_local_clip_rect,
specific_prim_address: prim_cache_address,
transform_id,
};
let prim_header_index = prim_headers.push(
&prim_header,
z_id,
batch_params.prim_user_data,
);
let bounding_rect = &prim_info.clip_chain.pic_clip_rect;
let transform_kind = transform_id.transform_kind();
self.add_segmented_prim_to_batch(
None,
PrimitiveOpacity::translucent(),
&batch_params,
BlendMode::None,
BlendMode::None,
batch_features,
prim_header_index,
bounding_rect,
transform_kind,
z_id,
prim_info.clip_task_index,
batch_filter,
ctx,
render_tasks,
);
}
// Adds a primitive to a batch.
// It can recursively call itself in some situations, for
// example if it encounters a picture where the items
@ -2391,21 +2324,7 @@ impl BatchBuilder {
);
}
PrimitiveInstanceKind::YuvImage { data_handle, segment_instance_index, is_compositor_surface, .. } => {
if is_compositor_surface {
self.emit_placeholder(
prim_rect,
prim_info,
batch_filter,
z_id,
transform_id,
batch_features,
ctx,
gpu_cache,
prim_headers,
render_tasks,
);
return;
}
debug_assert!(!is_compositor_surface);
let yuv_image_data = &ctx.data_stores.yuv_image[data_handle].kind;
let mut textures = TextureSet::UNTEXTURED;
@ -2510,21 +2429,8 @@ impl BatchBuilder {
);
}
PrimitiveInstanceKind::Image { data_handle, image_instance_index, is_compositor_surface, .. } => {
if is_compositor_surface {
self.emit_placeholder(
prim_rect,
prim_info,
batch_filter,
z_id,
transform_id,
batch_features,
ctx,
gpu_cache,
prim_headers,
render_tasks,
);
return;
}
debug_assert!(!is_compositor_surface);
let image_data = &ctx.data_stores.image[data_handle].kind;
let common_data = &ctx.data_stores.image[data_handle].common;
let image_instance = &ctx.prim_store.images[image_instance_index];

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

@ -579,11 +579,7 @@ impl CompositeState {
let surface = descriptor.resolve(resource_cache, tile_cache.current_tile_size);
(
CompositeTileSurface::Texture { surface },
// If a tile has compositor surface intersecting with it, we need to
// respect the tile.is_opaque property even if the overall tile cache
// is opaque. In this case, the tile.is_opaque property is required
// in order to ensure correct draw order with compositor surfaces.
tile.is_opaque || (!tile.has_compositor_surface && tile_cache.is_opaque()),
tile.is_opaque
)
}
};
@ -642,9 +638,29 @@ impl CompositeState {
);
}
// Add alpha tiles after opaque surfaces
if visible_alpha_tile_count > 0 {
self.descriptor.surfaces.push(
CompositeSurfaceDescriptor {
surface_id: sub_slice.native_surface.as_ref().map(|s| s.alpha),
clip_rect: surface_clip_rect,
transform: CompositorSurfaceTransform::translation(
tile_cache.device_position.x,
tile_cache.device_position.y,
0.0,
),
image_dependencies: [ImageDependency::INVALID; 3],
image_rendering: ImageRendering::CrispEdges,
tile_descriptors: alpha_tile_descriptors,
}
);
}
// For each compositor surface that was promoted, build the
// information required for the compositor to draw it
for external_surface in &tile_cache.external_surfaces {
for compositor_surface in &sub_slice.compositor_surfaces {
let external_surface = &compositor_surface.descriptor;
let clip_rect = external_surface
.clip_rect
.intersection(&device_clip_rect)
@ -722,25 +738,7 @@ impl CompositeState {
}
);
self.push_tile(tile, true);
}
// Add alpha / overlay tiles after compositor surfaces
if visible_alpha_tile_count > 0 {
self.descriptor.surfaces.push(
CompositeSurfaceDescriptor {
surface_id: sub_slice.native_surface.as_ref().map(|s| s.alpha),
clip_rect: surface_clip_rect,
transform: CompositorSurfaceTransform::translation(
tile_cache.device_position.x,
tile_cache.device_position.y,
0.0,
),
image_dependencies: [ImageDependency::INVALID; 3],
image_rendering: ImageRendering::CrispEdges,
tile_descriptors: alpha_tile_descriptors,
}
);
self.push_tile(tile, compositor_surface.is_opaque);
}
}
}
@ -869,7 +867,11 @@ impl CompositeState {
}
}
CompositeTileSurface::ExternalSurface { .. } => {
self.opaque_tiles.push(tile);
if is_opaque {
self.opaque_tiles.push(tile);
} else {
self.alpha_tiles.push(tile);
}
}
}
}

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

@ -469,7 +469,7 @@ impl FrameBuilder {
root_spatial_node_index,
root_spatial_node_index,
ROOT_SURFACE_INDEX,
&SubpixelMode::Allow,
SubpixelMode::Allow,
&mut frame_state,
&frame_context,
&mut scratch.primitive,
@ -890,30 +890,6 @@ pub fn build_render_pass(
}
};
// Determine the clear color for this picture cache.
// If the entire tile cache is opaque, we can skip clear completely.
// If it's the first layer, clear it to white to allow subpixel AA on that
// first layer even if it's technically transparent.
// Otherwise, clear to transparent and composite with alpha.
// TODO(gw): We can detect per-tile opacity for the clear color here
// which might be a significant win on some pages?
let forced_opaque = match tile_cache.background_color {
Some(color) => color.a >= 1.0,
None => false,
};
let mut clear_color = if forced_opaque {
Some(ColorF::WHITE)
} else {
Some(ColorF::TRANSPARENT)
};
// If this picture cache has a valid color backdrop, we will use
// that as the clear color, skipping the draw of the backdrop
// primitive (and anything prior to it) during batching.
if let Some(BackdropKind::Color { color }) = tile_cache.backdrop.kind {
clear_color = Some(color);
}
// Create an alpha batcher for each of the tasks of this picture.
let mut batchers = Vec::new();
for task_id in &task_ids {
@ -967,11 +943,30 @@ pub fn build_render_pass(
// designed to support batch merging, which isn't
// relevant for picture cache targets. We
// can restructure / tidy this up a bit.
let (scissor_rect, valid_rect) = match render_tasks[task_id].kind {
let (scissor_rect, valid_rect, clear_color) = match render_tasks[task_id].kind {
RenderTaskKind::Picture(ref info) => {
let mut clear_color = ColorF::TRANSPARENT;
// TODO(gw): The way we check the batch filter for is_primary is a bit hacky, tidy up somehow?
if let Some(batch_filter) = info.batch_filter {
if batch_filter.sub_slice_index.is_primary() {
if let Some(background_color) = tile_cache.background_color {
clear_color = background_color;
}
// If this picture cache has a valid color backdrop, we will use
// that as the clear color, skipping the draw of the backdrop
// primitive (and anything prior to it) during batching.
if let Some(BackdropKind::Color { color }) = tile_cache.backdrop.kind {
clear_color = color;
}
}
}
(
info.scissor_rect.expect("bug: must be set for cache tasks"),
info.valid_rect.expect("bug: must be set for cache tasks"),
clear_color,
)
}
_ => unreachable!(),
@ -988,7 +983,7 @@ pub fn build_render_pass(
let target = PictureCacheTarget {
surface: surface.clone(),
clear_color,
clear_color: Some(clear_color),
alpha_batch_container,
dirty_rect: scissor_rect,
valid_rect,

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

@ -184,7 +184,7 @@ use std::collections::HashMap;
pub const MAX_BLUR_RADIUS: f32 = 100.;
/// Specify whether a surface allows subpixel AA text rendering.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Copy, Clone)]
pub enum SubpixelMode {
/// This surface allows subpixel AA text
Allow,
@ -194,7 +194,6 @@ pub enum SubpixelMode {
/// with the excluded regions, and inside the allowed rect.
Conditional {
allowed_rect: PictureRect,
excluded_rects: Vec<PictureRect>,
},
}
@ -512,7 +511,7 @@ struct TilePostUpdateContext<'a> {
local_clip_rect: PictureRect,
/// The calculated backdrop information for this cache instance.
backdrop: BackdropInfo,
backdrop: Option<BackdropInfo>,
/// Information about opacity bindings from the picture cache.
opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
@ -526,18 +525,8 @@ struct TilePostUpdateContext<'a> {
/// The local rect of the overall picture cache
local_rect: PictureRect,
/// A list of the external surfaces that are present on this slice
external_surfaces: &'a [ExternalSurfaceDescriptor],
/// Pre-allocated z-id to assign to opaque tiles during post_update. We
/// use a different z-id for opaque/alpha tiles, so that compositor
/// surfaces (such as videos) can have a z-id between these values,
/// which allows compositor surfaces to occlude opaque tiles, but not
/// alpha tiles.
z_id_opaque: ZBufferId,
/// Pre-allocated z-id to assign to alpha tiles during post_update
z_id_alpha: ZBufferId,
/// Pre-allocated z-id to assign to tiles during post_update.
z_id: ZBufferId,
/// If true, the scale factor of the root transform for this picture
/// cache changed, so we need to invalidate the tile and re-render.
@ -581,9 +570,6 @@ struct PrimitiveDependencyInfo {
/// Spatial nodes references by the clip dependencies of this primitive.
spatial_nodes: SmallVec<[SpatialNodeIndex; 4]>,
/// If true, this primitive has been promoted to be a compositor surface.
is_compositor_surface: bool,
}
impl PrimitiveDependencyInfo {
@ -600,7 +586,6 @@ impl PrimitiveDependencyInfo {
prim_clip_box,
clips: SmallVec::new(),
spatial_nodes: SmallVec::new(),
is_compositor_surface: false,
}
}
}
@ -894,15 +879,9 @@ pub struct Tile {
background_color: Option<ColorF>,
/// The first reason the tile was invalidated this frame.
invalidation_reason: Option<InvalidationReason>,
/// If true, this tile has one or more compositor surfaces affecting it.
pub has_compositor_surface: bool,
/// The local space valid rect for any primitives found prior to the first compositor
/// surface that affects this tile.
bg_local_valid_rect: PictureBox2D,
/// The local space valid rect for any primitives found after the first compositor
/// surface that affects this tile.
fg_local_valid_rect: PictureBox2D,
/// z-buffer id for this tile, which is one of z_id_opaque or z_id_alpha, depending on tile opacity
/// The local space valid rect for all primitives that affect this tile.
local_valid_rect: PictureBox2D,
/// z-buffer id for this tile
pub z_id: ZBufferId,
/// The last frame this tile had its dependencies updated (dependency updating is
/// skipped if a tile is off-screen).
@ -933,9 +912,7 @@ impl Tile {
root: TileNode::new_leaf(Vec::new()),
background_color: None,
invalidation_reason: None,
has_compositor_surface: false,
bg_local_valid_rect: PictureBox2D::zero(),
fg_local_valid_rect: PictureBox2D::zero(),
local_valid_rect: PictureBox2D::zero(),
z_id: ZBufferId::invalid(),
last_updated_frame_id: FrameId::INVALID,
}
@ -1063,10 +1040,8 @@ impl Tile {
self.local_tile_rect.origin,
self.local_tile_rect.bottom_right(),
);
self.bg_local_valid_rect = PictureBox2D::zero();
self.fg_local_valid_rect = PictureBox2D::zero();
self.local_valid_rect = PictureBox2D::zero();
self.invalidation_reason = None;
self.has_compositor_surface = false;
self.world_tile_rect = ctx.pic_to_world_mapper
.map(&self.local_tile_rect)
@ -1126,26 +1101,10 @@ impl Tile {
return;
}
// If this primitive is a compositor surface, any tile it affects must be
// drawn as an overlay tile.
if info.is_compositor_surface {
self.has_compositor_surface = true;
} else {
// Incorporate the bounding rect of the primitive in the local valid rect
// for this tile. This is used to minimize the size of the scissor rect
// during rasterization and the draw rect during composition of partial tiles.
// Once we have encountered 1+ compositor surfaces affecting this tile, include
// this bounding rect in the foreground. Otherwise, include in the background rect.
// This allows us to determine if we found any primitives that are on top of the
// compositor surface(s) for this tile. If so, we need to draw the tile with alpha
// blending as an overlay.
if self.has_compositor_surface {
self.fg_local_valid_rect = self.fg_local_valid_rect.union(&info.prim_clip_box);
} else {
self.bg_local_valid_rect = self.bg_local_valid_rect.union(&info.prim_clip_box);
}
}
// Incorporate the bounding rect of the primitive in the local valid rect
// for this tile. This is used to minimize the size of the scissor rect
// during rasterization and the draw rect during composition of partial tiles.
self.local_valid_rect = self.local_valid_rect.union(&info.prim_clip_box);
// Include any image keys this tile depends on.
self.current_descriptor.images.extend_from_slice(&info.images);
@ -1244,12 +1203,8 @@ impl Tile {
return false;
}
// Calculate the overall valid rect for this tile, including both the foreground
// and background local valid rects.
self.current_descriptor.local_valid_rect =
self.bg_local_valid_rect
.union(&self.fg_local_valid_rect)
.to_rect();
// Calculate the overall valid rect for this tile.
self.current_descriptor.local_valid_rect = self.local_valid_rect.to_rect();
// TODO(gw): In theory, the local tile rect should always have an
// intersection with the overall picture rect. In practice,
@ -1307,31 +1262,13 @@ impl Tile {
let clipped_rect = self.current_descriptor.local_valid_rect
.intersection(&ctx.local_clip_rect)
.unwrap_or_else(PictureRect::zero);
let mut is_opaque = ctx.backdrop.opaque_rect.contains_rect(&clipped_rect);
if self.has_compositor_surface {
// If we found primitive(s) that are ordered _after_ the first compositor
// surface, _and_ intersect with any compositor surface, then we will need
// to draw this tile with alpha blending, as an overlay to the compositor surface.
let fg_world_valid_rect = ctx.pic_to_world_mapper
.map(&self.fg_local_valid_rect.to_rect())
.expect("bug: map fg local valid rect");
let fg_device_valid_rect = fg_world_valid_rect * ctx.global_device_pixel_scale;
let has_opaque_bg_color = self.background_color.map_or(false, |c| c.a >= 1.0);
let has_opaque_backdrop = ctx.backdrop.map_or(false, |b| b.opaque_rect.contains_rect(&clipped_rect));
let is_opaque = has_opaque_bg_color || has_opaque_backdrop;
for surface in ctx.external_surfaces {
if surface.device_rect.intersects(&fg_device_valid_rect) {
is_opaque = false;
break;
}
}
}
// Set the correct z_id for this tile based on opacity
if is_opaque {
self.z_id = ctx.z_id_opaque;
} else {
self.z_id = ctx.z_id_alpha;
}
// Set the correct z_id for this tile
self.z_id = ctx.z_id;
if is_opaque != self.is_opaque {
// If opacity changed, the native compositor surface and all tiles get invalidated.
@ -1394,7 +1331,7 @@ impl Tile {
// color tiles. We can definitely support this in DC, so this
// should be added as a follow up.
let is_simple_prim =
ctx.backdrop.kind.is_some() &&
ctx.backdrop.map_or(false, |b| b.kind.is_some()) &&
self.current_descriptor.prims.len() == 1 &&
self.is_opaque &&
supports_simple_prims;
@ -1404,7 +1341,7 @@ impl Tile {
// If we determine the tile can be represented by a color, set the
// surface unconditionally (this will drop any previously used
// texture cache backing surface).
match ctx.backdrop.kind {
match ctx.backdrop.unwrap().kind {
Some(BackdropKind::Color { color }) => {
TileSurface::Color {
color,
@ -2223,6 +2160,18 @@ impl SubSliceIndex {
}
}
/// Wrapper struct around an external surface descriptor with a little more information
/// that the picture caching code needs.
pub struct CompositorSurface {
// External surface descriptor used by compositing logic
pub descriptor: ExternalSurfaceDescriptor,
// The compositor surface rect + any intersecting prims. Later prims that intersect
// with this must be added to the next sub-slice.
prohibited_rect: PictureRect,
// If the compositor surface content is opaque.
pub is_opaque: bool,
}
/// A SubSlice represents a potentially overlapping set of tiles within a picture cache. Most
/// picture cache instances will have only a single sub-slice. The exception to this is when
/// a picture cache has compositor surfaces, in which case sub slices are used to interleave
@ -2234,6 +2183,9 @@ pub struct SubSlice {
/// not using native compositor, or if the surface was destroyed and needs
/// to be reallocated next time this surface contains valid tiles.
pub native_surface: Option<NativeSurface>,
/// List of compositor surfaces that have been promoted from primitives
/// in this tile cache.
pub compositor_surfaces: Vec<CompositorSurface>,
}
impl SubSlice {
@ -2242,9 +2194,16 @@ impl SubSlice {
SubSlice {
tiles: FastHashMap::default(),
native_surface: None,
compositor_surfaces: Vec::new(),
}
}
/// Reset the list of compositor surfaces that follow this sub-slice.
/// Built per-frame, since APZ may change whether an image is suitable to be a compositor surface.
fn reset(&mut self) {
self.compositor_surfaces.clear();
}
/// Resize the tile grid to match a new tile bounds
fn resize(&mut self, new_tile_rect: TileRect) -> FastHashMap<TileOffset, Box<Tile>> {
let mut old_tiles = mem::replace(&mut self.tiles, FastHashMap::default());
@ -2354,11 +2313,8 @@ pub struct TileCacheInstance {
/// The currently considered tile size override. Used to check if we should
/// re-evaluate tile size, even if the frame timer hasn't expired.
tile_size_override: Option<DeviceIntSize>,
/// List of external surfaces that have been promoted from primitives
/// in this tile cache.
pub external_surfaces: Vec<ExternalSurfaceDescriptor>,
/// z-buffer ID assigned to opaque tiles in this slice
pub z_id_opaque: ZBufferId,
/// z-buffer ID assigned to the opaque backdrop, if there is one, in this slice
pub z_id_backdrop: ZBufferId,
/// A cache of compositor surfaces that are retained between frames
pub external_native_surface_cache: FastHashMap<ExternalNativeSurfaceKey, ExternalNativeSurface>,
/// Current frame ID of this tile cache instance. Used for book-keeping / garbage collecting
@ -2374,10 +2330,14 @@ enum SurfacePromotionResult {
impl TileCacheInstance {
pub fn new(params: TileCacheParams) -> Self {
// TODO(gw): In this initial patch we just create a single sub-slice to retain existing
// behavior. Follow up patches will size this based on the number of compositor
// surfaces present in the supplied PrimitiveList.
let sub_slices = vec![SubSlice::new()];
// Determine how many sub-slices we need. Clamp to an arbitrary limit to ensure
// we don't create a huge number of OS compositor tiles and sub-slices.
let sub_slice_count = params.compositor_surface_count.min(MAX_COMPOSITOR_SURFACES) + 1;
let mut sub_slices = Vec::with_capacity(sub_slice_count);
for _ in 0 .. sub_slice_count {
sub_slices.push(SubSlice::new());
}
TileCacheInstance {
slice: params.slice,
@ -2415,8 +2375,7 @@ impl TileCacheInstance {
compare_cache: FastHashMap::default(),
device_position: DevicePoint::zero(),
tile_size_override: None,
external_surfaces: Vec::new(),
z_id_opaque: ZBufferId::invalid(),
z_id_backdrop: ZBufferId::invalid(),
external_native_surface_cache: FastHashMap::default(),
frame_id: FrameId::INVALID,
}
@ -2438,8 +2397,9 @@ impl TileCacheInstance {
// We should only receive updated state for matching slice key
assert_eq!(self.slice, params.slice);
// TODO(gw): Read the compositor surface count from the params in a follow up.
let required_sub_slice_count = 1;
// Determine how many sub-slices we need, based on how many compositor surface prims are
// in the supplied primitive list.
let required_sub_slice_count = params.compositor_surface_count.min(MAX_COMPOSITOR_SURFACES) + 1;
if self.sub_slices.len() != required_sub_slice_count {
self.tile_rect = TileRect::zero();
@ -2495,16 +2455,6 @@ impl TileCacheInstance {
}
}
/// Returns true if this tile cache is considered opaque.
pub fn is_opaque(&self) -> bool {
// If known opaque due to background clear color and being the first slice.
// The background_color will only be Some(..) if this is the first slice.
match self.background_color {
Some(color) => color.a >= 1.0,
None => false
}
}
/// Get the tile coordinates for a given rectangle.
fn get_tile_coords_for_rect(
&self,
@ -2538,15 +2488,17 @@ impl TileCacheInstance {
frame_context: &FrameVisibilityContext,
frame_state: &mut FrameVisibilityState,
) -> WorldRect {
self.external_surfaces.clear();
self.surface_index = surface_index;
self.local_rect = pic_rect;
self.local_clip_rect = PictureRect::max_rect();
// Opaque surfaces get the first z_id. Compositor surfaces then get
// allocated a z_id each. After all compositor surfaces are added,
// then we allocate a z_id for alpha tiles.
self.z_id_opaque = frame_state.composite_state.z_generator.next();
for sub_slice in &mut self.sub_slices {
sub_slice.reset();
}
// Backdrop surfaces get the first z_id. Subslices and compositor surfaces then get
// allocated a z_id each.
self.z_id_backdrop = frame_state.composite_state.z_generator.next();
// Reset the opaque rect + subpixel mode, as they are calculated
// during the prim dependency checks.
@ -2949,6 +2901,7 @@ impl TileCacheInstance {
prim_clip_chain: &ClipChainInstance,
prim_spatial_node_index: SpatialNodeIndex,
on_picture_surface: bool,
sub_slice_index: usize,
frame_context: &FrameVisibilityContext,
) -> SurfacePromotionResult {
// Check if this primitive _wants_ to be promoted to a compositor surface.
@ -2957,7 +2910,7 @@ impl TileCacheInstance {
}
// For now, only support a small (arbitrary) number of compositor surfaces.
if self.external_surfaces.len() == MAX_COMPOSITOR_SURFACES {
if sub_slice_index == MAX_COMPOSITOR_SURFACES {
return SurfacePromotionResult::Failed;
}
@ -2995,10 +2948,12 @@ impl TileCacheInstance {
fn setup_compositor_surfaces_yuv(
&mut self,
sub_slice_index: usize,
prim_info: &mut PrimitiveDependencyInfo,
flags: PrimitiveFlags,
local_prim_rect: LayoutRect,
prim_spatial_node_index: SpatialNodeIndex,
pic_clip_rect: PictureRect,
frame_context: &FrameVisibilityContext,
image_dependencies: &[ImageDependency;3],
api_keys: &[ImageKey; 3],
@ -3022,10 +2977,12 @@ impl TileCacheInstance {
}
self.setup_compositor_surfaces_impl(
sub_slice_index,
prim_info,
flags,
local_prim_rect,
prim_spatial_node_index,
pic_clip_rect,
frame_context,
ExternalSurfaceDependency::Yuv {
image_dependencies: *image_dependencies,
@ -3037,15 +2994,18 @@ impl TileCacheInstance {
resource_cache,
composite_state,
image_rendering,
true,
)
}
fn setup_compositor_surfaces_rgb(
&mut self,
sub_slice_index: usize,
prim_info: &mut PrimitiveDependencyInfo,
flags: PrimitiveFlags,
local_prim_rect: LayoutRect,
prim_spatial_node_index: SpatialNodeIndex,
pic_clip_rect: PictureRect,
frame_context: &FrameVisibilityContext,
image_dependency: ImageDependency,
api_key: ImageKey,
@ -3072,11 +3032,16 @@ impl TileCacheInstance {
gpu_cache,
);
let is_opaque = resource_cache.get_image_properties(api_key)
.map_or(false, |properties| properties.descriptor.is_opaque());
self.setup_compositor_surfaces_impl(
sub_slice_index,
prim_info,
flags,
local_prim_rect,
prim_spatial_node_index,
pic_clip_rect,
frame_context,
ExternalSurfaceDependency::Rgb {
image_dependency,
@ -3086,6 +3051,7 @@ impl TileCacheInstance {
resource_cache,
composite_state,
image_rendering,
is_opaque,
)
}
@ -3093,19 +3059,20 @@ impl TileCacheInstance {
// and the non-compositor path should be used to draw it instead.
fn setup_compositor_surfaces_impl(
&mut self,
sub_slice_index: usize,
prim_info: &mut PrimitiveDependencyInfo,
flags: PrimitiveFlags,
local_prim_rect: LayoutRect,
prim_spatial_node_index: SpatialNodeIndex,
pic_clip_rect: PictureRect,
frame_context: &FrameVisibilityContext,
dependency: ExternalSurfaceDependency,
api_keys: &[ImageKey; 3],
resource_cache: &mut ResourceCache,
composite_state: &mut CompositeState,
image_rendering: ImageRendering,
is_opaque: bool,
) -> bool {
prim_info.is_compositor_surface = true;
let map_local_to_surface = SpaceMapper::new_with_target(
self.spatial_node_index,
prim_spatial_node_index,
@ -3213,7 +3180,7 @@ impl TileCacheInstance {
Some(_external_image) => {
// If we have a suitable external image, then create an external
// surface to attach to.
resource_cache.create_compositor_external_surface(true)
resource_cache.create_compositor_external_surface(is_opaque)
}
None => {
// Otherwise create a normal compositor surface and a single
@ -3222,7 +3189,7 @@ impl TileCacheInstance {
resource_cache.create_compositor_surface(
DeviceIntPoint::zero(),
native_surface_size,
true,
is_opaque,
);
let tile_id = NativeTileId {
@ -3284,19 +3251,28 @@ impl TileCacheInstance {
}
};
// For compositor surfaces, if we didn't find an earlier sub-slice to add to,
// we know we can append to the current slice.
debug_assert!(sub_slice_index < self.sub_slices.len() - 1);
let sub_slice = &mut self.sub_slices[sub_slice_index];
// Each compositor surface allocates a unique z-id
self.external_surfaces.push(ExternalSurfaceDescriptor {
local_rect: prim_info.prim_clip_box.to_rect(),
local_clip_rect: prim_info.prim_clip_box.to_rect(),
dependency,
image_rendering,
device_rect,
surface_rect,
clip_rect,
transform: transform.cast_unit(),
z_id: composite_state.z_generator.next(),
native_surface_id,
update_params,
sub_slice.compositor_surfaces.push(CompositorSurface {
prohibited_rect: pic_clip_rect,
is_opaque,
descriptor: ExternalSurfaceDescriptor {
local_rect: prim_info.prim_clip_box.to_rect(),
local_clip_rect: prim_info.prim_clip_box.to_rect(),
dependency,
image_rendering,
device_rect,
surface_rect,
clip_rect,
transform: transform.cast_unit(),
z_id: ZBufferId::invalid(),
native_surface_id,
update_params,
},
});
true
@ -3385,6 +3361,30 @@ impl TileCacheInstance {
pic_clip_rect.to_box2d(),
);
let mut sub_slice_index = self.sub_slices.len() - 1;
// Only need to evaluate sub-slice regions if we have compositor surfaces present
if sub_slice_index > 0 {
// Find the first sub-slice we can add this primitive to (we want to add
// prims to the primary surface if possible, so they get subpixel AA).
for (i, sub_slice) in self.sub_slices.iter_mut().enumerate() {
let mut intersects_prohibited_region = false;
for surface in &mut sub_slice.compositor_surfaces {
if pic_clip_rect.intersects(&surface.prohibited_rect) {
surface.prohibited_rect = surface.prohibited_rect.union(&pic_clip_rect);
intersects_prohibited_region = true;
}
}
if !intersects_prohibited_region {
sub_slice_index = i;
break;
}
}
}
// Include the prim spatial node, if differs relative to cache root.
if prim_spatial_node_index != self.spatial_node_index {
prim_info.spatial_nodes.push(prim_spatial_node_index);
@ -3458,6 +3458,7 @@ impl TileCacheInstance {
prim_clip_chain,
prim_spatial_node_index,
on_picture_surface,
sub_slice_index,
frame_context) {
SurfacePromotionResult::Failed => {
}
@ -3484,10 +3485,12 @@ impl TileCacheInstance {
if promote_to_surface {
promote_to_surface = self.setup_compositor_surfaces_rgb(
sub_slice_index,
&mut prim_info,
image_key.common.flags,
local_prim_rect,
prim_spatial_node_index,
pic_clip_rect,
frame_context,
ImageDependency {
key: image_data.key,
@ -3502,14 +3505,17 @@ impl TileCacheInstance {
);
}
if !promote_to_surface {
*is_compositor_surface = promote_to_surface;
if promote_to_surface {
prim_instance.vis.state = VisibilityState::Culled;
return;
} else {
prim_info.images.push(ImageDependency {
key: image_data.key,
generation: resource_cache.get_image_generation(image_data.key),
});
}
*is_compositor_surface = promote_to_surface;
}
PrimitiveInstanceKind::YuvImage { data_handle, ref mut is_compositor_surface, .. } => {
let prim_data = &data_stores.yuv_image[data_handle];
@ -3518,6 +3524,7 @@ impl TileCacheInstance {
prim_clip_chain,
prim_spatial_node_index,
on_picture_surface,
sub_slice_index,
frame_context) {
SurfacePromotionResult::Failed => false,
SurfacePromotionResult::Success{flip_y} => !flip_y,
@ -3543,10 +3550,12 @@ impl TileCacheInstance {
}
promote_to_surface = self.setup_compositor_surfaces_yuv(
sub_slice_index,
&mut prim_info,
prim_data.common.flags,
local_prim_rect,
prim_spatial_node_index,
pic_clip_rect,
frame_context,
&image_dependencies,
&prim_data.kind.yuv_key,
@ -3560,7 +3569,15 @@ impl TileCacheInstance {
);
}
if !promote_to_surface {
// Store on the YUV primitive instance whether this is a promoted surface.
// This is used by the batching code to determine whether to draw the
// image to the content tiles, or just a transparent z-write.
*is_compositor_surface = promote_to_surface;
if promote_to_surface {
prim_instance.vis.state = VisibilityState::Culled;
return;
} else {
prim_info.images.extend(
prim_data.kind.yuv_key.iter().map(|key| {
ImageDependency {
@ -3570,12 +3587,6 @@ impl TileCacheInstance {
})
);
}
// Store on the YUV primitive instance whether this is a promoted surface.
// This is used by the batching code to determine whether to draw the
// image to the content tiles, or just a transparent z-write.
*is_compositor_surface = promote_to_surface;
}
PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
let border_data = &data_stores.image_border[data_handle].kind;
@ -3635,8 +3646,6 @@ impl TileCacheInstance {
// checks to see if it matches all conditions to be a backdrop.
let mut vis_flags = PrimitiveVisibilityFlags::empty();
// TODO(gw): Select the right sub_slice!
let sub_slice_index = 0;
let sub_slice = &mut self.sub_slices[sub_slice_index];
if let Some(backdrop_candidate) = backdrop_candidate {
@ -3670,9 +3679,10 @@ impl TileCacheInstance {
}
};
if is_suitable_backdrop
&& self.external_surfaces.is_empty()
&& !prim_clip_chain.needs_mask {
if sub_slice_index == 0 &&
is_suitable_backdrop &&
sub_slice.compositor_surfaces.is_empty() &&
!prim_clip_chain.needs_mask {
if backdrop_candidate.opaque_rect.contains_rect(&self.backdrop.opaque_rect) {
self.backdrop.opaque_rect = backdrop_candidate.opaque_rect;
@ -3762,8 +3772,10 @@ impl TileCacheInstance {
}
fn calculate_subpixel_mode(&self) -> SubpixelMode {
let has_opaque_bg_color = self.background_color.map_or(false, |c| c.a >= 1.0);
// If the overall tile cache is known opaque, subpixel AA is allowed everywhere
if self.is_opaque() {
if has_opaque_bg_color {
return SubpixelMode::Allow;
}
@ -3779,26 +3791,15 @@ impl TileCacheInstance {
return SubpixelMode::Allow;
}
// If none of the simple cases above match, we need to build a list
// of excluded rects (compositor surfaces) and a valid inclusion rect
// (known opaque area) where we can support subpixel AA.
// If none of the simple cases above match, we need test where we can support subpixel AA.
// TODO(gw): In future, it may make sense to have > 1 inclusion rect,
// but this handles the common cases.
// TODO(gw): If a text run gets animated such that it's moving in a way that is
// sometimes intersecting with the video rect, this can result in subpixel
// AA flicking on/off for that text run. It's probably very rare, but
// something we should handle in future.
let excluded_rects = self.external_surfaces
.iter()
.map(|s| {
s.local_rect
})
.collect();
SubpixelMode::Conditional {
allowed_rect: self.backdrop.opaque_rect,
excluded_rects,
}
}
@ -3837,35 +3838,12 @@ impl TileCacheInstance {
// Since we register the entire backdrop rect, use the opaque z-id for the
// picture cache slice.
frame_state.composite_state.register_occluder(
self.z_id_opaque,
self.z_id_backdrop,
world_backdrop_rect,
);
}
}
// Register any external compositor surfaces as potential occluders. This
// is especially useful when viewing video in full-screen mode, as it is
// able to occlude every background tile (avoiding allocation, rasterizion
// and compositing).
for external_surface in &self.external_surfaces {
let local_surface_rect = external_surface.local_rect
.intersection(&external_surface.local_clip_rect)
.and_then(|r| {
r.intersection(&self.local_clip_rect)
});
if let Some(local_surface_rect) = local_surface_rect {
let world_surface_rect = map_pic_to_world
.map(&local_surface_rect)
.expect("bug: unable to map external surface to world space");
frame_state.composite_state.register_occluder(
external_surface.z_id,
world_surface_rect,
);
}
}
// A simple GC of the native external surface cache, to remove and free any
// surfaces that were not referenced during the update_prim_dependencies pass.
self.external_native_surface_cache.retain(|_, surface| {
@ -3910,21 +3888,16 @@ impl TileCacheInstance {
frame_context.spatial_tree,
);
// All compositor surfaces have allocated a z_id, so reserve a z_id for alpha tiles.
let z_id_alpha = frame_state.composite_state.z_generator.next();
let ctx = TilePostUpdateContext {
let mut ctx = TilePostUpdateContext {
pic_to_world_mapper,
global_device_pixel_scale: frame_context.global_device_pixel_scale,
local_clip_rect: self.local_clip_rect,
backdrop: self.backdrop,
backdrop: Some(self.backdrop),
opacity_bindings: &self.opacity_bindings,
color_bindings: &self.color_bindings,
current_tile_size: self.current_tile_size,
local_rect: self.local_rect,
external_surfaces: &self.external_surfaces,
z_id_opaque: self.z_id_opaque,
z_id_alpha,
z_id: self.z_id_backdrop,
invalidate_all: root_scale_changed || frame_context.config.force_invalidation,
};
@ -3941,6 +3914,44 @@ impl TileCacheInstance {
for tile in sub_slice.tiles.values_mut() {
tile.post_update(&ctx, &mut state, frame_context);
}
for compositor_surface in &mut sub_slice.compositor_surfaces {
compositor_surface.descriptor.z_id = state.composite_state.z_generator.next();
}
// After the first sub-slice, the backdrop is no longer relevant
ctx.backdrop = None;
ctx.z_id = state.composite_state.z_generator.next();
}
// Register any opaque external compositor surfaces as potential occluders. This
// is especially useful when viewing video in full-screen mode, as it is
// able to occlude every background tile (avoiding allocation, rasterizion
// and compositing).
for sub_slice in &self.sub_slices {
for compositor_surface in &sub_slice.compositor_surfaces {
if compositor_surface.is_opaque {
let local_surface_rect = compositor_surface
.descriptor
.local_rect
.intersection(&compositor_surface.descriptor.local_clip_rect)
.and_then(|r| {
r.intersection(&self.local_clip_rect)
});
if let Some(local_surface_rect) = local_surface_rect {
let world_surface_rect = map_pic_to_world
.map(&local_surface_rect)
.expect("bug: unable to map external surface to world space");
frame_state.composite_state.register_occluder(
compositor_surface.descriptor.z_id,
world_surface_rect,
);
}
}
}
}
}
}
@ -4727,7 +4738,7 @@ impl PicturePrimitive {
surface_spatial_node_index: SpatialNodeIndex,
raster_spatial_node_index: SpatialNodeIndex,
parent_surface_index: SurfaceIndex,
parent_subpixel_mode: &SubpixelMode,
parent_subpixel_mode: SubpixelMode,
frame_state: &mut FrameBuildingState,
frame_context: &FrameBuildingContext,
scratch: &mut PrimitiveScratchBuffer,
@ -5848,7 +5859,7 @@ impl PicturePrimitive {
Some(RasterConfig { ref composite_mode, .. }) => {
let subpixel_mode = match composite_mode {
PictureCompositeMode::TileCache { slice_id } => {
tile_caches[&slice_id].subpixel_mode.clone()
tile_caches[&slice_id].subpixel_mode
}
PictureCompositeMode::Blit(..) |
PictureCompositeMode::ComponentTransferFilter(..) |
@ -5876,18 +5887,16 @@ impl PicturePrimitive {
// Both parent and this surface unconditionally allow subpixel AA
SubpixelMode::Allow
}
(SubpixelMode::Allow, SubpixelMode::Conditional { allowed_rect, excluded_rects }) => {
(SubpixelMode::Allow, SubpixelMode::Conditional { allowed_rect }) => {
// Parent allows, but we are conditional subpixel AA
SubpixelMode::Conditional {
allowed_rect,
excluded_rects,
}
}
(SubpixelMode::Conditional { allowed_rect, excluded_rects }, SubpixelMode::Allow) => {
(SubpixelMode::Conditional { allowed_rect }, SubpixelMode::Allow) => {
// Propagate conditional subpixel mode to child pictures that allow subpixel AA
SubpixelMode::Conditional {
allowed_rect: *allowed_rect,
excluded_rects: excluded_rects.clone(),
allowed_rect,
}
}
(SubpixelMode::Conditional { .. }, SubpixelMode::Conditional { ..}) => {

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

@ -23,7 +23,7 @@ use crate::gpu_cache::{GpuCacheHandle, GpuDataRequest};
use crate::gpu_types::{BrushFlags};
use crate::internal_types::{FastHashMap, PlaneSplitAnchor};
use crate::picture::{PicturePrimitive, SliceId, TileCacheLogger, ClusterFlags, SurfaceRenderTasks};
use crate::picture::{PrimitiveList, PrimitiveCluster, SurfaceIndex, TileCacheInstance};
use crate::picture::{PrimitiveList, PrimitiveCluster, SurfaceIndex, TileCacheInstance, SubpixelMode};
use crate::prim_store::gradient::{GRADIENT_FP_STOPS, FastLinearGradientCacheKey, GradientStopKey, CachedGradientSegment};
use crate::prim_store::gradient::LinearGradientPrimitive;
use crate::prim_store::line_dec::MAX_LINE_DECORATION_RESOLUTION;
@ -172,7 +172,7 @@ fn prepare_prim_for_render(
pic_context.surface_spatial_node_index,
pic_context.raster_spatial_node_index,
pic_context.surface_index,
&pic_context.subpixel_mode,
pic_context.subpixel_mode,
frame_state,
frame_context,
scratch,
@ -361,22 +361,49 @@ fn prepare_interned_prim_for_render(
let pic = &store.pictures[pic_context.pic_index.0];
let surface = &frame_state.surfaces[pic_context.surface_index.0];
let prim_info = &prim_instance.vis;
let root_scaling_factor = match pic.raster_config {
Some(ref raster_config) => raster_config.root_scaling_factor,
None => 1.0
};
// If subpixel AA is disabled due to the backing surface the glyphs
// are being drawn onto, disable it (unless we are using the
// specifial subpixel mode that estimates background color).
let allow_subpixel = match prim_instance.vis.state {
VisibilityState::Culled |
VisibilityState::Unset |
VisibilityState::Coarse { .. } |
VisibilityState::PassThrough => {
panic!("bug: invalid visibility state");
}
VisibilityState::Detailed { ref filter, .. } => {
// For now, we only allow subpixel AA on primary sub-slices. In future we
// may support other sub-slices if we find content that does this.
if filter.sub_slice_index.is_primary() {
match pic_context.subpixel_mode {
SubpixelMode::Allow => true,
SubpixelMode::Deny => false,
SubpixelMode::Conditional { allowed_rect } => {
// Conditional mode allows subpixel AA to be enabled for this
// text run, so long as it's inside the allowed rect.
allowed_rect.contains_rect(&prim_instance.vis.clip_chain.pic_clip_rect)
}
}
} else {
false
}
}
};
run.request_resources(
prim_offset,
prim_info.clip_chain.pic_clip_rect,
&prim_data.font,
&prim_data.glyphs,
&transform.to_transform().with_destination::<_>(),
surface,
prim_spatial_node_index,
root_scaling_factor,
&pic_context.subpixel_mode,
allow_subpixel,
frame_state.resource_cache,
frame_state.gpu_cache,
frame_context.spatial_tree,

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

@ -3,14 +3,14 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{ColorF, FontInstanceFlags, GlyphInstance, RasterSpace, Shadow};
use api::units::{LayoutToWorldTransform, LayoutVector2D, PictureRect};
use api::units::{LayoutToWorldTransform, LayoutVector2D};
use crate::scene_building::{CreateShadow, IsVisible};
use crate::frame_builder::FrameBuildingState;
use crate::glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT};
use crate::gpu_cache::GpuCache;
use crate::intern;
use crate::internal_types::LayoutPrimitiveInfo;
use crate::picture::{SubpixelMode, SurfaceInfo};
use crate::picture::SurfaceInfo;
use crate::prim_store::{PrimitiveOpacity, PrimitiveScratchBuffer};
use crate::prim_store::{PrimitiveStore, PrimKeyCommonData, PrimTemplateCommonData};
use crate::renderer::{MAX_VERTEX_TEXTURE_WIDTH};
@ -244,9 +244,8 @@ impl TextRunPrimitive {
surface: &SurfaceInfo,
spatial_node_index: SpatialNodeIndex,
transform: &LayoutToWorldTransform,
subpixel_mode: &SubpixelMode,
mut allow_subpixel: bool,
raster_space: RasterSpace,
prim_rect: PictureRect,
root_scaling_factor: f32,
spatial_tree: &SpatialTree,
) -> bool {
@ -354,21 +353,6 @@ impl TextRunPrimitive {
..specified_font.clone()
};
// If subpixel AA is disabled due to the backing surface the glyphs
// are being drawn onto, disable it (unless we are using the
// specifial subpixel mode that estimates background color).
let mut allow_subpixel = match subpixel_mode {
SubpixelMode::Allow => true,
SubpixelMode::Deny => false,
SubpixelMode::Conditional { allowed_rect, excluded_rects } => {
// Conditional mode allows subpixel AA to be enabled for this
// text run, so long as it doesn't intersect with any of the
// cutout rectangles in the list, and it's inside the allowed rect.
allowed_rect.contains_rect(&prim_rect) &&
excluded_rects.iter().all(|rect| !rect.intersects(&prim_rect))
}
};
// If we are using special estimated background subpixel blending, then
// we can allow it regardless of what the surface says.
allow_subpixel |= self.used_font.bg_color.a != 0;
@ -419,14 +403,13 @@ impl TextRunPrimitive {
pub fn request_resources(
&mut self,
prim_offset: LayoutVector2D,
prim_rect: PictureRect,
specified_font: &FontInstance,
glyphs: &[GlyphInstance],
transform: &LayoutToWorldTransform,
surface: &SurfaceInfo,
spatial_node_index: SpatialNodeIndex,
root_scaling_factor: f32,
subpixel_mode: &SubpixelMode,
allow_subpixel: bool,
resource_cache: &mut ResourceCache,
gpu_cache: &mut GpuCache,
spatial_tree: &SpatialTree,
@ -442,9 +425,8 @@ impl TextRunPrimitive {
surface,
spatial_node_index,
transform,
subpixel_mode,
allow_subpixel,
raster_space,
prim_rect,
root_scaling_factor,
spatial_tree,
);

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

@ -1,3 +1,2 @@
# TODO: Enable these once non-opaque compositor surface support is enabled!
# == basic.yaml basic-ref.yaml
# == too-many-surfaces.yaml too-many-surfaces-ref.yaml
fuzzy(2,500) == basic.yaml basic-ref.yaml
== too-many-surfaces.yaml too-many-surfaces-ref.yaml