Bug 1721111 - Introduce picture graph and port initial picture pass to use it r=gfx-reviewers,bradwerth,kvark

In future, we want to persist a lot more of the information about
primitive instances between display lists. This will allow us to
implement a number of optimizations and improvements to the scene
and frame building code (such as reducing per-frame per-prim work
that is typically mostly redundant, supporting prims other than
pictures that can contain child primitives, unifying and optimizing
primitive dependency updates).

As a step towards that, this patch introduces a picture graph which
is very similar to the idea of the render task graph builder. With
this in place, we're able to run picture updates without using
recursion, and ensuring we update pictures in the correct order
that the pass requires. This will allow us to un-tangle a lot of
the existing scene building, visibility, prep and batching pass
complexity, in order to be able to implement the above.

Differential Revision: https://phabricator.services.mozilla.com/D120191
This commit is contained in:
Glenn Watson 2021-07-21 20:25:33 +00:00
Родитель 2553bda47b
Коммит 371f4cd13b
6 изменённых файлов: 289 добавлений и 174 удалений

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

@ -13,7 +13,7 @@ use crate::gpu_cache::{GpuCache, GpuCacheHandle};
use crate::gpu_types::{PrimitiveHeaders, TransformPalette, ZBufferIdGenerator};
use crate::gpu_types::TransformData;
use crate::internal_types::{FastHashMap, PlaneSplitter};
use crate::picture::{DirtyRegion, PictureUpdateState, SliceId, TileCacheInstance};
use crate::picture::{DirtyRegion, SliceId, TileCacheInstance};
use crate::picture::{SurfaceInfo, SurfaceIndex, ROOT_SURFACE_INDEX, SurfaceRenderTasks, SubSliceIndex};
use crate::picture::{BackdropKind, SubpixelMode, TileCacheLogger, RasterConfig, PictureCompositeMode};
use crate::prepare::prepare_primitives;
@ -366,26 +366,24 @@ impl FrameBuilder {
let mut surfaces = scratch.frame.surfaces.take();
surfaces.push(root_surface);
// The first major pass of building a frame is to walk the picture
// tree. This pass must be quick (it should never touch individual
// primitives). For now, all we do here is determine which pictures
// will create surfaces. In the future, this will be expanded to
// set up render tasks, determine scaling of surfaces, and detect
// which surfaces have valid cached surfaces that don't need to
// be rendered this frame.
for pic_index in &scene.tile_cache_pictures {
PictureUpdateState::update_all(
&mut scratch.picture,
&mut surfaces,
*pic_index,
&mut scene.prim_store.pictures,
&frame_context,
gpu_cache,
&scene.clip_store,
data_stores,
tile_caches,
);
}
scene.picture_graph.build_update_passes(
&mut scene.prim_store.pictures,
&frame_context,
);
scene.picture_graph.assign_surfaces(
&mut scene.prim_store.pictures,
&mut surfaces,
tile_caches,
&frame_context,
);
scene.picture_graph.propagate_bounding_rects(
&mut scene.prim_store.pictures,
&mut surfaces,
&frame_context,
data_stores,
);
{
profile_scope!("UpdateVisibility");

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

@ -105,6 +105,7 @@ mod hit_test;
mod internal_types;
mod lru_cache;
mod picture;
mod picture_graph;
mod prepare;
mod prim_store;
mod print_tree;

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

@ -3474,7 +3474,7 @@ impl TileCacheInstance {
PrimitiveInstanceKind::Picture { pic_index,.. } => {
// Pictures can depend on animated opacity bindings.
let pic = &pictures[pic_index.0];
if let Some(PictureCompositeMode::Filter(Filter::Opacity(binding, _))) = pic.requested_composite_mode {
if let Some(PictureCompositeMode::Filter(Filter::Opacity(binding, _))) = pic.composite_mode {
prim_info.opacity_bindings.push(binding.into());
}
}
@ -4026,114 +4026,6 @@ impl PictureScratchBuffer {
pub fn recycle(&mut self, recycler: &mut Recycler) {
recycler.recycle_vec(&mut self.surface_stack);
}
}
/// Maintains a stack of picture and surface information, that
/// is used during the initial picture traversal.
pub struct PictureUpdateState<'a> {
surfaces: &'a mut Vec<SurfaceInfo>,
surface_stack: Vec<SurfaceIndex>,
}
impl<'a> PictureUpdateState<'a> {
pub fn update_all(
buffers: &mut PictureScratchBuffer,
surfaces: &'a mut Vec<SurfaceInfo>,
pic_index: PictureIndex,
picture_primitives: &mut [PicturePrimitive],
frame_context: &FrameBuildingContext,
gpu_cache: &mut GpuCache,
clip_store: &ClipStore,
data_stores: &mut DataStores,
tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
) {
profile_scope!("UpdatePictures");
profile_marker!("UpdatePictures");
let mut state = PictureUpdateState {
surfaces,
surface_stack: buffers.surface_stack.take().cleared(),
};
state.surface_stack.push(SurfaceIndex(0));
state.update(
pic_index,
picture_primitives,
frame_context,
gpu_cache,
clip_store,
data_stores,
tile_caches,
);
buffers.surface_stack = state.surface_stack.take();
}
/// Return the current surface
fn current_surface(&self) -> &SurfaceInfo {
&self.surfaces[self.surface_stack.last().unwrap().0]
}
/// Return the current surface (mutable)
fn current_surface_mut(&mut self) -> &mut SurfaceInfo {
&mut self.surfaces[self.surface_stack.last().unwrap().0]
}
/// Push a new surface onto the update stack.
fn push_surface(
&mut self,
surface: SurfaceInfo,
) -> SurfaceIndex {
let surface_index = SurfaceIndex(self.surfaces.len());
self.surfaces.push(surface);
self.surface_stack.push(surface_index);
surface_index
}
/// Pop a surface on the way up the picture traversal
fn pop_surface(&mut self) -> SurfaceIndex{
self.surface_stack.pop().unwrap()
}
/// Update a picture, determining surface configuration,
/// rasterization roots, and (in future) whether there
/// are cached surfaces that can be used by this picture.
fn update(
&mut self,
pic_index: PictureIndex,
picture_primitives: &mut [PicturePrimitive],
frame_context: &FrameBuildingContext,
gpu_cache: &mut GpuCache,
clip_store: &ClipStore,
data_stores: &mut DataStores,
tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
) {
if let Some(prim_list) = picture_primitives[pic_index.0].pre_update(
self,
frame_context,
tile_caches,
) {
for child_pic_index in &prim_list.child_pictures {
self.update(
*child_pic_index,
picture_primitives,
frame_context,
gpu_cache,
clip_store,
data_stores,
tile_caches,
);
}
picture_primitives[pic_index.0].post_update(
prim_list,
self,
frame_context,
data_stores,
);
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
@ -4637,7 +4529,7 @@ pub struct PicturePrimitive {
pub secondary_render_task_id: Option<RenderTaskId>,
/// How this picture should be composited.
/// If None, don't composite - just draw directly on parent surface.
pub requested_composite_mode: Option<PictureCompositeMode>,
pub composite_mode: Option<PictureCompositeMode>,
pub raster_config: Option<RasterConfig>,
pub context_3d: Picture3DContext<OrderedPictureChild>,
@ -4696,7 +4588,7 @@ impl PicturePrimitive {
pt.add_item(format!("precise_local_rect: {:?}", self.precise_local_rect));
pt.add_item(format!("spatial_node_index: {:?}", self.spatial_node_index));
pt.add_item(format!("raster_config: {:?}", self.raster_config));
pt.add_item(format!("requested_composite_mode: {:?}", self.requested_composite_mode));
pt.add_item(format!("composite_mode: {:?}", self.composite_mode));
for child_pic_index in &self.prim_list.child_pictures {
pictures[child_pic_index.0].print(pictures, *child_pic_index, pt);
@ -4726,7 +4618,7 @@ impl PicturePrimitive {
}
fn resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool {
match self.requested_composite_mode {
match self.composite_mode {
Some(PictureCompositeMode::Filter(ref mut filter)) => {
match *filter {
Filter::Opacity(ref binding, ref mut value) => {
@ -4742,7 +4634,7 @@ impl PicturePrimitive {
}
pub fn is_visible(&self) -> bool {
match self.requested_composite_mode {
match self.composite_mode {
Some(PictureCompositeMode::Filter(ref filter)) => {
filter.is_visible()
}
@ -4755,7 +4647,7 @@ impl PicturePrimitive {
// method to be part of the PictureOptions, and
// avoid adding new parameters here.
pub fn new_image(
requested_composite_mode: Option<PictureCompositeMode>,
composite_mode: Option<PictureCompositeMode>,
context_3d: Picture3DContext<OrderedPictureChild>,
apply_local_clip_rect: bool,
flags: PrimitiveFlags,
@ -4768,7 +4660,7 @@ impl PicturePrimitive {
state: None,
primary_render_task_id: None,
secondary_render_task_id: None,
requested_composite_mode,
composite_mode,
raster_config: None,
context_3d,
extra_gpu_data_handles: SmallVec::new(),
@ -5909,7 +5801,7 @@ impl PicturePrimitive {
#[cfg(feature = "capture")]
{
if frame_context.debug_flags.contains(DebugFlags::TILE_CACHE_LOGGING_DBG) {
if let Some(PictureCompositeMode::TileCache { slice_id }) = self.requested_composite_mode {
if let Some(PictureCompositeMode::TileCache { slice_id }) = self.composite_mode {
if let Some(ref tile_cache) = tile_caches.get(&slice_id) {
// extract just the fields that we're interested in
let mut tile_cache_tiny = TileCacheInstanceSerializer {
@ -6187,22 +6079,16 @@ impl PicturePrimitive {
}
}
/// Called during initial picture traversal, before we know the
/// bounding rect of children. It is possible to determine the
/// surface / raster config now though.
fn pre_update(
/// Do initial checks to determine whether this picture should be drawn as part of the
/// frame build.
pub fn pre_update_visibility_check(
&mut self,
state: &mut PictureUpdateState,
frame_context: &FrameBuildingContext,
tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
) -> Option<PrimitiveList> {
// Reset raster config in case we early out below.
self.raster_config = None;
) -> bool {
// Resolve animation properties, and early out if the filter
// properties make this picture invisible.
if !self.resolve_scene_properties(frame_context.scene_properties) {
return None;
return false;
}
// For out-of-preserve-3d pictures, the backface visibility is determined by
@ -6213,19 +6099,36 @@ impl PicturePrimitive {
if let Picture3DContext::Out = self.context_3d {
match frame_context.spatial_tree.get_local_visible_face(self.spatial_node_index) {
VisibleFace::Front => {}
VisibleFace::Back => return None,
VisibleFace::Back => return false,
}
}
}
// See if this picture actually needs a surface for compositing.
// TODO(gw): FPC: Remove the actual / requested composite mode distinction.
let actual_composite_mode = self.requested_composite_mode.clone();
true
}
if let Some(composite_mode) = actual_composite_mode {
/// Called during initial picture traversal, before we know the
/// bounding rect of children. It is possible to determine the
/// surface / raster config now though.
pub fn assign_surface(
&mut self,
frame_context: &FrameBuildingContext,
tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
current_surface_index: SurfaceIndex,
surfaces: &mut Vec<SurfaceInfo>,
) -> SurfaceIndex {
// Reset raster config in case we early out below.
self.raster_config = None;
// Assume we don't create a surface by default.
// TODO: This (and all the separate surface code can disappear once composite_mode is not an Option)
let mut surface_index = current_surface_index;
if let Some(ref composite_mode) = self.composite_mode {
// Retrieve the positioning node information for the parent surface.
let parent_raster_node_index = state.current_surface().raster_spatial_node_index;
let parent_device_pixel_scale = state.current_surface().device_pixel_scale;
let current_surface = &surfaces[current_surface_index.0];
let parent_raster_node_index = current_surface.raster_spatial_node_index;
let parent_device_pixel_scale = current_surface.device_pixel_scale;
let surface_spatial_node_index = self.spatial_node_index;
let surface_to_parent_transform = frame_context.spatial_tree
@ -6314,7 +6217,7 @@ impl PicturePrimitive {
if self.options.inflate_if_required {
match composite_mode {
PictureCompositeMode::Filter(Filter::Blur(width, height)) => {
let blur_radius = f32::max(clamp_blur_radius(width, scale_factors), clamp_blur_radius(height, scale_factors));
let blur_radius = f32::max(clamp_blur_radius(*width, scale_factors), clamp_blur_radius(*height, scale_factors));
// The amount of extra space needed for primitives inside
// this picture to ensure the visibility check is correct.
inflation_factor = blur_radius * BLUR_SAMPLE_SCALE;
@ -6357,31 +6260,34 @@ impl PicturePrimitive {
scale_factors,
);
surface_index = SurfaceIndex(surfaces.len());
surfaces.push(surface);
self.raster_config = Some(RasterConfig {
composite_mode,
composite_mode: composite_mode.clone(),
establishes_raster_root,
surface_index: state.push_surface(surface),
surface_index,
root_scaling_factor: 1.0,
clipped_bounding_rect: WorldRect::zero(),
});
}
Some(mem::replace(&mut self.prim_list, PrimitiveList::empty()))
surface_index
}
/// Called after updating child pictures during the initial
/// picture traversal.
fn post_update(
/// picture traversal. Bounding rects are propagated from
/// child pictures up to parent picture surfaces, so that the
/// parent bounding rect includes any dynamic picture bounds.
pub fn propagate_bounding_rect(
&mut self,
prim_list: PrimitiveList,
state: &mut PictureUpdateState,
surface_index: SurfaceIndex,
parent_surface_index: SurfaceIndex,
surfaces: &mut [SurfaceInfo],
frame_context: &FrameBuildingContext,
data_stores: &mut DataStores,
) {
// Restore the pictures list used during recursion.
self.prim_list = prim_list;
let surface = state.current_surface_mut();
let surface = &mut surfaces[surface_index.0];
for cluster in &mut self.prim_list.clusters {
cluster.flags.remove(ClusterFlags::IS_VISIBLE);
@ -6486,7 +6392,6 @@ impl PicturePrimitive {
// rect into the parent surface coordinate space, and propagate that up
// to the parent.
if let Some(ref mut raster_config) = self.raster_config {
let surface = state.current_surface_mut();
// Inflate the local bounding rect if required by the filter effect.
if self.options.inflate_if_required {
surface.rect = raster_config.composite_mode.inflate_picture_rect(surface.rect, surface.scale_factors);
@ -6494,10 +6399,6 @@ impl PicturePrimitive {
let mut surface_rect = surface.rect * Scale::new(1.0);
// Pop this surface from the stack
let surface_index = state.pop_surface();
debug_assert_eq!(surface_index, raster_config.surface_index);
// Set the estimated and precise local rects. The precise local rect
// may be changed again during frame visibility.
self.estimated_local_rect = surface_rect;
@ -6516,7 +6417,7 @@ impl PicturePrimitive {
}
// Propagate up to parent surface, now that we know this surface's static rect
let parent_surface = state.current_surface_mut();
let parent_surface = &mut surfaces[parent_surface_index.0];
parent_surface.map_local_to_surface.set_target_spatial_node(
self.spatial_node_index,
frame_context.spatial_tree,

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

@ -0,0 +1,198 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::frame_builder::FrameBuildingContext;
use crate::internal_types::FastHashMap;
use crate::prim_store::PictureIndex;
use crate::picture::{PicturePrimitive, SurfaceIndex, ROOT_SURFACE_INDEX, SurfaceInfo};
use crate::picture::{TileCacheInstance, SliceId};
use crate::render_backend::DataStores;
use smallvec::SmallVec;
#[derive(Debug)]
pub struct PictureInfo {
pub update_pass: Option<usize>,
pub surface_index: SurfaceIndex,
pub parent: Option<PictureIndex>,
}
/// A graph of picture dependencies, allowing updates to be processed without recursion
/// by building a list of passes.
pub struct PictureGraph {
roots: Vec<PictureIndex>,
pic_info: Vec<PictureInfo>,
update_passes: Vec<Vec<PictureIndex>>,
}
impl PictureGraph {
pub fn new() -> Self {
PictureGraph {
roots: Vec::new(),
pic_info: Vec::new(),
update_passes: Vec::new(),
}
}
/// Add a root picture to the graph
pub fn add_root(
&mut self,
pic_index: PictureIndex,
) {
self.roots.push(pic_index);
}
/// Build a list of update passes based on the dependencies between pictures
pub fn build_update_passes(
&mut self,
pictures: &mut [PicturePrimitive],
frame_context: &FrameBuildingContext
) {
self.pic_info.clear();
self.pic_info.reserve(pictures.len());
for _ in 0 .. pictures.len() {
self.pic_info.push(PictureInfo {
update_pass: None,
parent: None,
surface_index: ROOT_SURFACE_INDEX,
})
};
let mut max_pass_index = 0;
for pic_index in &self.roots {
assign_update_pass(
*pic_index,
None,
0,
pictures,
&mut self.pic_info,
&mut max_pass_index,
frame_context,
);
}
let pass_count = max_pass_index + 1;
self.update_passes.resize_with(pass_count, Vec::new);
for (pic_index, info) in self.pic_info.iter().enumerate() {
if let Some(update_pass) = info.update_pass {
let pass = &mut self.update_passes[update_pass];
pass.push(PictureIndex(pic_index));
}
}
}
/// Assign surfaces and scale factors to each picture (root -> leaf ordered pass)
pub fn assign_surfaces(
&mut self,
pictures: &mut [PicturePrimitive],
surfaces: &mut Vec<SurfaceInfo>,
tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
frame_context: &FrameBuildingContext,
) {
for pass in &self.update_passes {
for pic_index in pass {
let parent = self.pic_info[pic_index.0].parent;
let parent_surface_index = parent.map_or(ROOT_SURFACE_INDEX, |parent| {
self.pic_info[parent.0].surface_index
});
let info = &mut self.pic_info[pic_index.0];
info.surface_index = pictures[pic_index.0].assign_surface(
frame_context,
tile_caches,
parent_surface_index,
surfaces,
);
}
}
}
/// Propegate bounding rects from pictures to parents (leaf -> root ordered pass)
pub fn propagate_bounding_rects(
&mut self,
pictures: &mut [PicturePrimitive],
surfaces: &mut [SurfaceInfo],
frame_context: &FrameBuildingContext,
data_stores: &mut DataStores,
) {
for pass in self.update_passes.iter().rev() {
for pic_index in pass {
let parent = self.pic_info[pic_index.0].parent;
let surface_index = self.pic_info[pic_index.0].surface_index;
let parent_surface_index = parent.map_or(ROOT_SURFACE_INDEX, |parent| {
self.pic_info[parent.0].surface_index
});
pictures[pic_index.0].propagate_bounding_rect(
surface_index,
parent_surface_index,
surfaces,
frame_context,
data_stores,
);
}
}
}
}
/// Recursive function that assigns pictures to the earliest pass possible that they
/// can be processed in, while maintaining dependency ordering.
fn assign_update_pass(
pic_index: PictureIndex,
parent_pic_index: Option<PictureIndex>,
pass: usize,
pictures: &mut [PicturePrimitive],
pic_info: &mut [PictureInfo],
max_pass_index: &mut usize,
frame_context: &FrameBuildingContext
) {
let pic = &mut pictures[pic_index.0];
let info = &mut pic_info[pic_index.0];
info.parent = parent_pic_index;
let can_be_drawn = match info.update_pass {
Some(update_pass) => {
// No point in recursing into paths in the graph if this picture already
// has been set to update after this pass.
if update_pass > pass {
return;
}
true
}
None => {
// Check if this picture can be dropped from the graph we're building this frame
pic.pre_update_visibility_check(frame_context)
}
};
if can_be_drawn {
info.update_pass = Some(pass);
*max_pass_index = pass.max(*max_pass_index);
let mut child_pictures: SmallVec<[PictureIndex; 8]> = SmallVec::new();
child_pictures.extend_from_slice(&pic.prim_list.child_pictures);
for child_pic_index in child_pictures {
assign_update_pass(
child_pic_index,
Some(pic_index),
pass + 1,
pictures,
pic_info,
max_pass_index,
frame_context,
);
}
}
}

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

@ -13,6 +13,7 @@ use crate::spatial_tree::SpatialTree;
use crate::frame_builder::{ChasePrimitive, FrameBuilderConfig};
use crate::hit_test::{HitTester, HitTestingScene, HitTestingSceneStats};
use crate::internal_types::FastHashMap;
use crate::picture_graph::PictureGraph;
use crate::prim_store::{PrimitiveStore, PrimitiveStoreStats, PictureIndex};
use crate::tile_cache::TileCacheConfig;
use std::sync::Arc;
@ -276,6 +277,7 @@ pub struct BuiltScene {
pub hit_testing_scene: Arc<HitTestingScene>,
pub tile_cache_config: TileCacheConfig,
pub tile_cache_pictures: Vec<PictureIndex>,
pub picture_graph: PictureGraph,
}
impl BuiltScene {
@ -291,6 +293,7 @@ impl BuiltScene {
hit_testing_scene: Arc::new(HitTestingScene::new(&HitTestingSceneStats::empty())),
tile_cache_config: TileCacheConfig::new(0),
tile_cache_pictures: Vec::new(),
picture_graph: PictureGraph::new(),
config: FrameBuilderConfig {
default_font_render_mode: FontRenderMode::Mono,
dual_source_blending_is_enabled: true,

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

@ -58,6 +58,7 @@ use crate::intern::Interner;
use crate::internal_types::{FastHashMap, LayoutPrimitiveInfo, Filter};
use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureOptions};
use crate::picture::{BlitReason, OrderedPictureChild, PrimitiveList};
use crate::picture_graph::PictureGraph;
use crate::prim_store::{PrimitiveInstance, register_prim_chase_id};
use crate::prim_store::{PrimitiveInstanceKind, NinePatchDescriptor, PrimitiveStore};
use crate::prim_store::{InternablePrimitive, SegmentInstanceIndex, PictureIndex};
@ -510,6 +511,12 @@ pub struct SceneBuilder<'a> {
/// edge cases (e.g. SVG filter) where we can accept slightly incorrect
/// behaviour in favour of getting the common case right.
snap_to_device: SpaceSnapper,
/// A DAG that represents dependencies between picture primitives. This builds
/// a set of passes to run various picture processing passes in during frame
/// building, in a way that pictures are processed before (or after) their
/// dependencies, without relying on recursion for those passes.
picture_graph: PictureGraph,
}
impl<'a> SceneBuilder<'a> {
@ -560,6 +567,7 @@ impl<'a> SceneBuilder<'a> {
quality_settings: view.quality_settings,
tile_cache_builder: TileCacheBuilder::new(),
snap_to_device,
picture_graph: PictureGraph::new(),
};
builder.build_all(&root_pipeline);
@ -572,6 +580,11 @@ impl<'a> SceneBuilder<'a> {
builder.interners,
);
// Add all the tile cache pictures as roots of the picture graph
for pic_index in &tile_cache_pictures {
builder.picture_graph.add_root(*pic_index);
}
BuiltScene {
has_root_pipeline: scene.has_root_pipeline(),
pipeline_epochs: scene.pipeline_epochs.clone(),
@ -584,6 +597,7 @@ impl<'a> SceneBuilder<'a> {
config: builder.config,
tile_cache_config,
tile_cache_pictures,
picture_graph: builder.picture_graph,
}
}
@ -3520,7 +3534,7 @@ impl<'a> SceneBuilder<'a> {
}
let (pic_index, instance) = flattened_items?;
self.prim_store.pictures[pic_index.0].requested_composite_mode = Some(PictureCompositeMode::Blit(BlitReason::BACKDROP));
self.prim_store.pictures[pic_index.0].composite_mode = Some(PictureCompositeMode::Blit(BlitReason::BACKDROP));
backdrop_root.expect("no backdrop root found")
.prim_list
.add_prim(