/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Contains functionality to help building the render task graph from a series of off-screen //! surfaces that are created during the prepare pass, and other surface related types and //! helpers.
/// Maximum blur radius for blur filter const MAX_BLUR_RADIUS: f32 = 100.;
/// An index into the surface array #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct SurfaceIndex(pub usize);
/// Specify whether a surface allows subpixel AA text rendering. #[derive(Debug, Copy, Clone)] pubenum SubpixelMode { /// This surface allows subpixel AA text
Allow, /// Subpixel AA text cannot be drawn on this surface
Deny, /// Subpixel AA can be drawn on this surface, if not intersecting /// with the excluded regions, and inside the allowed rect.
Conditional {
allowed_rect: PictureRect,
prohibited_rect: PictureRect,
},
}
/// Information about an offscreen surface. For now, /// it contains information about the size and coordinate /// system of the surface. In the future, it will contain /// information about the contents of the surface, which /// will allow surfaces to be cached / retained between /// frames and display lists. pubstruct SurfaceInfo { /// A local rect defining the size of this surface, in the /// coordinate system of the parent surface. This contains /// the unclipped bounding rect of child primitives. /// /// SNAPTODO: This rect is built by mapping per-cluster bounding /// rects (and child-surface coverage rects) into this surface's /// picture space via `map_local_to_picture`. Even once the source /// cluster bound is a true union of per-prim *snapped* local /// rects, the resulting `unclipped_local_rect` is not guaranteed /// to be snapped: any 2D transform that isn't an axis-aligned, /// integer-pixel translation between the cluster/child-surface /// spatial node and this surface's spatial node will produce /// sub-pixel edges in picture space. Float blur-margin inflation /// inside `composite_mode.get_coverage` can also break the snap. /// Consumers that need a snapped value will either need to /// re-snap in surface space or restrict the snap path to surfaces /// where the cross-space mapping preserves grid alignment (see /// `SurfaceInfo.allow_snapping`). pub unclipped_local_rect: PictureRect, /// The local space coverage of child primitives after they are /// are clipped to their owning clip-chain. pub clipped_local_rect: PictureRect, /// The (conservative) valid part of this surface rect. Used /// to reduce the size of render target allocation. pub clipping_rect: PictureRect, /// The rectangle to use for culling and clipping. pub culling_rect: VisRect, /// Helper structs for mapping local rects in different /// coordinate systems into the picture coordinates. pub map_local_to_picture: SpaceMapper<LayoutPixel, PicturePixel>, /// The positioning node for the surface itself, pub surface_spatial_node_index: SpatialNodeIndex, /// The rasterization root for this surface. pub raster_spatial_node_index: SpatialNodeIndex, /// The spatial node for culling and clipping (anything using VisPixel). /// TODO: Replace with the raster spatial node. pub visibility_spatial_node_index: SpatialNodeIndex, /// The device pixel ratio specific to this surface. pub device_pixel_scale: DevicePixelScale, /// The scale factors of the surface to world transform. pub world_scale_factors: (f32, f32), /// Local scale factors surface to raster transform pub local_scale: (f32, f32), /// If true, we know this surface is completely opaque. pub is_opaque: bool, /// Whether content rasterized into this surface is snapped to the device /// pixel grid at frame time. True for tile caches (snapped against the /// scroll-stable cache node) and root-snapping surfaces (raster node is /// root). False for a non-snapping raster root (preserve-3d / perspective / /// huge-scale, `enable_snapping == false`): snapping against its own scaled /// node would use only the tiny local scale and collapse content to zero, /// so content is left unsnapped there instead. pub allow_snapping: bool, /// If true, the scissor rect must be set when drawing this surface pub force_scissor_rect: bool,
}
let pic_bounds = map_surface_to_world
.unmap(&map_surface_to_world.bounds)
.unwrap_or_else(PictureRect::max_rect);
let map_local_to_picture = SpaceMapper::new(
surface_spatial_node_index,
pic_bounds,
);
// TODO: replace the root with raster space. let visibility_spatial_node_index = spatial_tree.root_reference_frame_index();
SurfaceInfo {
unclipped_local_rect: PictureRect::zero(),
clipped_local_rect: PictureRect::zero(),
is_opaque: false,
clipping_rect: PictureRect::zero(),
map_local_to_picture,
raster_spatial_node_index,
surface_spatial_node_index,
visibility_spatial_node_index,
device_pixel_scale,
world_scale_factors,
local_scale,
allow_snapping,
force_scissor_rect, // TODO: At the moment all culling is done in world space but // but the plan is to move it to raster space.
culling_rect: world_rect.cast_unit(),
}
}
/// Clamps the blur radius depending on scale factors. pubfn clamp_blur_radius(
&self,
x_blur_radius: f32,
y_blur_radius: f32,
) -> (f32, f32) { // Clamping must occur after scale factors are applied, but scale factors are not applied // until later on. To clamp the blur radius, we first apply the scale factors and then clamp // and finally revert the scale factors.
let sx_blur_radius = x_blur_radius * self.local_scale.0; let sy_blur_radius = y_blur_radius * self.local_scale.1;
if largest_scaled_blur_radius > MAX_BLUR_RADIUS { let sf = MAX_BLUR_RADIUS / largest_scaled_blur_radius;
(x_blur_radius * sf, y_blur_radius * sf)
} else { // Return the original blur radius to avoid any rounding errors
(x_blur_radius, y_blur_radius)
}
}
pubfn update_culling_rect(
&mutself,
parent_culling_rect: VisRect,
composite_mode: &PictureCompositeMode,
frame_context: &FrameVisibilityContext,
) { // Set the default culling rect to be the parent, in case we fail // any mappings below due to weird perspective or invalid transforms. self.culling_rect = parent_culling_rect;
iflet PictureCompositeMode::Filter(Filter::Blur { width, height, should_inflate, .. }) = composite_mode { if *should_inflate { // Space mapping vis <-> picture space let map_surface_to_vis = SpaceMapper::new_with_target( // TODO: switch from root to raster space.
frame_context.root_spatial_node_index, self.surface_spatial_node_index,
parent_culling_rect,
frame_context.spatial_tree,
);
// Unmap the parent culling rect to surface space. Note that this may be // quite conservative in the case of a complex transform, especially perspective. iflet Some(local_parent_culling_rect) = map_surface_to_vis.unmap(&parent_culling_rect) { let (width_factor, height_factor) = self.clamp_blur_radius(*width, *height);
// Inflate by the local-space amount this surface extends. let expanded_rect: PictureBox2D = local_parent_culling_rect.inflate(
width_factor.ceil() * BLUR_SAMPLE_SCALE,
height_factor.ceil() * BLUR_SAMPLE_SCALE,
);
// Map back to the expected vis-space culling rect iflet Some(rect) = map_surface_to_vis.map(&expanded_rect) { self.culling_rect = rect;
}
}
}
}
}
pubfn map_to_device_rect(
&self,
picture_rect: &PictureRect,
spatial_tree: &SpatialTree,
) -> DeviceRect { let raster_rect = ifself.raster_spatial_node_index != self.surface_spatial_node_index { // Currently, the surface's spatial node can be different from its raster node only // for surfaces in the root coordinate system for snapping reasons. // See `PictureInstance::assign_surface`.
assert_eq!(self.device_pixel_scale.0, 1.0);
assert_eq!(self.raster_spatial_node_index, spatial_tree.root_reference_frame_index());
let pic_to_raster = SpaceMapper::new_with_target( self.raster_spatial_node_index, self.surface_spatial_node_index,
WorldRect::max_rect(),
spatial_tree,
);
/// Clip and transform a local rect to a device rect suitable for allocating /// a child off-screen surface of this surface (e.g. for clip-masks) pubfn get_surface_rect(
&self,
local_rect: &PictureRect,
spatial_tree: &SpatialTree,
) -> Option<DeviceIntRect> { let local_rect = match local_rect.intersection(&self.clipping_rect) {
Some(rect) => rect,
None => return None,
};
let raster_rect = ifself.raster_spatial_node_index != self.surface_spatial_node_index {
assert_eq!(self.device_pixel_scale.0, 1.0);
let local_to_world = SpaceMapper::new_with_target(
spatial_tree.root_reference_frame_index(), self.surface_spatial_node_index,
WorldRect::max_rect(),
spatial_tree,
);
local_to_world.map(&local_rect).unwrap()
} else { // The content should have been culled out earlier.
assert!(self.device_pixel_scale.0 > 0.0);
local_rect.cast_unit()
};
let surface_rect = (raster_rect * self.device_pixel_scale).round_out().to_i32(); if surface_rect.is_empty() { // The local_rect computed above may have non-empty size that is very // close to zero. Due to limited arithmetic precision, the SpaceMapper // might transform the near-zero-sized rect into a zero-sized one. return None;
}
Some(surface_rect)
}
}
// Information about the render task(s) for a given tile #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct SurfaceTileDescriptor { /// Target render task for commands added to this tile. This is changed /// each time a sub-graph is encountered on this tile pub current_task_id: RenderTaskId, /// The compositing task for this tile, if required. This is only needed /// when a tile contains one or more sub-graphs. pub composite_task_id: Option<RenderTaskId>, /// Dirty rect for this tile pub dirty_rect: PictureRect,
}
// Details of how a surface is rendered pubenum SurfaceDescriptorKind { // Picture cache tiles
Tiled {
tiles: FastHashMap<TileKey, SurfaceTileDescriptor>,
}, // A single surface (e.g. for an opacity filter)
Simple {
render_task_id: RenderTaskId,
dirty_rect: PictureRect,
}, // A surface with 1+ intermediate tasks (e.g. blur)
Chained {
render_task_id: RenderTaskId,
root_task_id: RenderTaskId,
dirty_rect: PictureRect,
},
}
// Describes how a surface is rendered pubstruct SurfaceDescriptor {
kind: SurfaceDescriptorKind,
}
// Describes a list of command buffers that we are adding primitives to // for a given surface. These are created from a command buffer builder // as an optimization - skipping the indirection pic_task -> cmd_buffer_index struct CommandBufferTargets {
available_cmd_buffers: Vec<Vec<(PictureRect, CommandBufferIndex)>>,
}
fn init(
&mutself,
cb: &CommandBufferBuilder,
rg_builder: &RenderTaskGraphBuilder,
) { for available_cmd_buffers in &mutself.available_cmd_buffers {
available_cmd_buffers.clear();
}
match cb.kind {
CommandBufferBuilderKind::Tiled { ref tiles, .. } => { for (key, desc) in tiles { let task = rg_builder.get_task(desc.current_task_id); match task.kind {
RenderTaskKind::Picture(ref info) => { let available_cmd_buffers = &mutself.available_cmd_buffers[key.sub_slice_index.as_usize()];
available_cmd_buffers.push((desc.dirty_rect, info.cmd_buffer_index));
}
_ => unreachable!("bug: not a picture"),
}
}
}
CommandBufferBuilderKind::Simple { render_task_id, dirty_rect, .. } => { let task = rg_builder.get_task(render_task_id); match task.kind {
RenderTaskKind::Picture(ref info) => { for sub_slice_buffer in &mutself.available_cmd_buffers {
sub_slice_buffer.push((dirty_rect, info.cmd_buffer_index));
}
}
_ => unreachable!("bug: not a picture"),
}
}
CommandBufferBuilderKind::Invalid => {}
};
}
/// For a given rect and sub-slice, get a list of command buffers to write commands to fn get_cmd_buffer_targets_for_rect(
&mutself,
rect: &PictureRect,
sub_slice_index: SubSliceIndex,
targets: &mut Vec<CommandBufferIndex>,
) -> bool {
for (dirty_rect, cmd_buffer_index) in &self.available_cmd_buffers[sub_slice_index.as_usize()] { if dirty_rect.intersects(rect) {
targets.push(*cmd_buffer_index);
}
}
!targets.is_empty()
}
}
// Main helper interface to build a graph of surfaces. In future patches this // will support building sub-graphs. pubstruct SurfaceBuilder { // The currently set cmd buffer targets (updated during push/pop)
current_cmd_buffers: CommandBufferTargets, // Stack of surfaces that are parents to the current targets
builder_stack: Vec<CommandBufferBuilder>, // A map of the output render tasks from any sub-graphs that haven't // been consumed by BackdropRender prims yet pub sub_graph_output_map: FastHashMap<PictureIndex, RenderTaskId>,
}
/// Register the current surface as the source of a resolve for the task sub-graph that /// is currently on the surface builder stack. pubfn register_resolve_source(
&mutself,
) { let surface_task_id = matchself.builder_stack.last().unwrap().kind {
CommandBufferBuilderKind::Tiled { .. } | CommandBufferBuilderKind::Invalid => {
panic!("bug: only supported for non-tiled surfaces");
}
CommandBufferBuilderKind::Simple { render_task_id, .. } => render_task_id,
};
for builder inself.builder_stack.iter_mut().rev() { if builder.establishes_sub_graph {
assert_eq!(builder.resolve_source, None);
builder.resolve_source = Some(surface_task_id); return;
}
}
unreachable!("bug: resolve source with no sub-graph");
}
// Add a child render task (e.g. a render task cache item, or a clip mask) as a // dependency of the current surface pubfn add_child_render_task(
&mutself,
child_task_id: RenderTaskId,
rg_builder: &mut RenderTaskGraphBuilder,
) { let builder = self.builder_stack.last().unwrap();
// Add a picture render task as a dependency of the parent surface. This is a // special case with extra complexity as the root of the surface may change // when inside a sub-graph. It's currently only needed for drop-shadow effects. pubfn add_picture_render_task(
&mutself,
child_task_id: RenderTaskId,
) { self.builder_stack
.last_mut()
.unwrap()
.extra_dependencies
.push(child_task_id);
}
// Get a list of command buffer indices that primitives should be pushed // to for a given current visbility / dirty state pubfn get_cmd_buffer_targets_for_prim(
&mutself,
vis: &PrimitiveDrawHeader,
targets: &mut Vec<CommandBufferIndex>,
) -> bool {
targets.clear();
pubfn pop_empty_surface(&mutself) { let builder = self.builder_stack.pop().unwrap();
assert!(!builder.establishes_sub_graph);
}
// Finish adding primitives and child tasks to a surface and pop it off the stack pubfn pop_surface(
&mutself,
pic_index: PictureIndex,
rg_builder: &mut RenderTaskGraphBuilder,
cmd_buffers: &mut CommandBufferList,
) { let builder = self.builder_stack.pop().unwrap();
if builder.establishes_sub_graph { // If we are popping a sub-graph off the stack the dependency setup is rather more complex... match builder.kind {
CommandBufferBuilderKind::Tiled { .. } | CommandBufferBuilderKind::Invalid => {
unreachable!("bug: sub-graphs can only be simple surfaces");
}
CommandBufferBuilderKind::Simple { render_task_id: child_render_task_id, root_task_id: child_root_task_id, .. } => { // Get info about the resolve operation to copy from parent surface or tiles to the picture cache task iflet Some(resolve_task_id) = builder.resolve_source { letmut src_task_ids = Vec::new();
// Make the output of the sub-graph a dependency of the new replacement tile task let _old = self.sub_graph_output_map.insert(
pic_index,
child_root_task_id.unwrap_or(child_render_task_id),
);
debug_assert!(_old.is_none());
// Set up dependencies for the sub-graph. The basic concepts below are the same, but for // tiled surfaces are a little more complex as there are multiple tasks to set up. // (a) Set up new task(s) on parent surface that write to the same location // (b) Set up a resolve target to copy from parent surface tasks(s) to the resolve target // (c) Make the old parent surface tasks input dependencies of the resolve target // (d) Make the sub-graph output an input dependency of the new task(s).
// For each tile in parent surface for key in keys { let descriptor = tiles.remove(&key).unwrap(); let parent_task_id = descriptor.current_task_id; let parent_task = rg_builder.get_task_mut(parent_task_id);
match parent_task.location {
RenderTaskLocation::Unallocated { .. } | RenderTaskLocation::Existing { .. } => { // Get info about the parent tile task location and params let location = RenderTaskLocation::Existing {
parent_task_id,
size: parent_task.location.size(),
};
let pic_task = match parent_task.kind {
RenderTaskKind::Picture(refmut pic_task) => { let cmd_buffer_index = cmd_buffers.create_cmd_buffer(); let new_pic_task = pic_task.duplicate(cmd_buffer_index);
// Add the resolve src to copy from tile -> picture input task
src_task_ids.push(parent_task_id);
new_pic_task
}
_ => panic!("bug: not a picture"),
};
// Make the existing tile an input dependency of the resolve target
rg_builder.add_dependency(
resolve_task_id,
parent_task_id,
);
// Create the new task to replace the tile task let new_task_id = rg_builder.add().init(
RenderTask::new(
location, // draw to same place
RenderTaskKind::Picture(pic_task),
),
);
// Ensure that the parent task will get scheduled earlier during // pass assignment since we are reusing the existing surface, // even though it's not technically needed for rendering order.
rg_builder.add_dependency(
new_task_id,
parent_task_id,
);
// Update the surface builder with the now current target for future primitives
tiles.insert(
key,
SurfaceTileDescriptor {
current_task_id: new_task_id,
..descriptor
},
);
}
RenderTaskLocation::Static { .. } => { // Update the surface builder with the now current target for future primitives
tiles.insert(
key,
descriptor,
);
}
_ => {
panic!("bug: unexpected task location");
}
}
}
}
CommandBufferBuilderKind::Simple { render_task_id: refmut parent_task_id, root_task_id: ref parent_root_task_id, .. } => { let parent_task = rg_builder.get_task_mut(*parent_task_id);
// Get info about the parent tile task location and params let location = RenderTaskLocation::Existing {
parent_task_id: *parent_task_id,
size: parent_task.location.size(),
}; let pic_task = match parent_task.kind {
RenderTaskKind::Picture(refmut pic_task) => { let cmd_buffer_index = cmd_buffers.create_cmd_buffer();
let new_pic_task = pic_task.duplicate(cmd_buffer_index);
// Add the resolve src to copy from tile -> picture input task
src_task_ids.push(*parent_task_id);
new_pic_task
}
_ => panic!("bug: not a picture"),
};
// Make the existing surface an input dependency of the resolve target
rg_builder.add_dependency(
resolve_task_id,
*parent_task_id,
);
// Create the new task to replace the parent surface task let new_task_id = rg_builder.add().init(
RenderTask::new(
location, // draw to same place
RenderTaskKind::Picture(pic_task),
),
);
// Ensure that the parent task will get scheduled earlier during // pass assignment since we are reusing the existing surface, // even though it's not technically needed for rendering order.
rg_builder.add_dependency(
new_task_id,
*parent_task_id,
);
// If the parent is a chained surface (e.g. a CSS blur or drop-shadow // filter), its filter pass (root_task_id) reads from the same texture // as parent_task_id. Ensure it executes after new_task_id has written // post-backdrop-capture content (e.g. backdrop-filter children) to // that texture, otherwise those primitives will be missing from the // filter output. iflet Some(root_task_id) = *parent_root_task_id {
rg_builder.add_dependency(
root_task_id,
new_task_id,
);
}
// Update the surface builder with the now current target for future primitives
*parent_task_id = new_task_id;
}
CommandBufferBuilderKind::Invalid => {
unreachable!();
}
}
let dest_task = rg_builder.get_task_mut(resolve_task_id);
match dest_task.kind {
RenderTaskKind::Picture(refmut dest_task_info) => {
assert!(dest_task_info.resolve_op.is_none());
dest_task_info.resolve_op = Some(ResolveOp {
src_task_ids,
dest_task_id: resolve_task_id,
})
}
_ => {
unreachable!("bug: not a picture");
}
}
}
// This can occur if there is an edge case where the resolve target is found // not visible even though the filter chain was (for example, in the case of // an extreme scale causing floating point inaccuracies). Adding a dependency // here is also a safety in case for some reason the backdrop render primitive // doesn't pick up the dependency, ensuring that it gets scheduled and freed // as early as possible. matchself.builder_stack.last().unwrap().kind {
CommandBufferBuilderKind::Tiled { ref tiles } => { // For a tiled render task, add as a dependency to every tile. for (_, descriptor) in tiles {
rg_builder.add_dependency(
descriptor.current_task_id,
child_root_task_id.unwrap_or(child_render_task_id),
);
}
}
CommandBufferBuilderKind::Simple { render_task_id: parent_task_id, .. } => {
rg_builder.add_dependency(
parent_task_id,
child_root_task_id.unwrap_or(child_render_task_id),
);
}
CommandBufferBuilderKind::Invalid => {
unreachable!();
}
}
}
}
} else { match builder.kind {
CommandBufferBuilderKind::Tiled { ref tiles } => { for (_, descriptor) in tiles { iflet Some(composite_task_id) = descriptor.composite_task_id {
rg_builder.add_dependency(
composite_task_id,
descriptor.current_task_id,
);
let composite_task = rg_builder.get_task_mut(composite_task_id); match composite_task.kind {
RenderTaskKind::TileComposite(refmut info) => {
info.task_id = Some(descriptor.current_task_id);
}
_ => unreachable!("bug: not a tile composite"),
}
}
}
}
CommandBufferBuilderKind::Simple { render_task_id: child_task_id, root_task_id: child_root_task_id, .. } => { matchself.builder_stack.last().unwrap().kind {
CommandBufferBuilderKind::Tiled { ref tiles } => { // For a tiled render task, add as a dependency to every tile. for (_, descriptor) in tiles {
rg_builder.add_dependency(
descriptor.current_task_id,
child_root_task_id.unwrap_or(child_task_id),
);
}
}
CommandBufferBuilderKind::Simple { render_task_id: parent_task_id, .. } => {
rg_builder.add_dependency(
parent_task_id,
child_root_task_id.unwrap_or(child_task_id),
);
}
CommandBufferBuilderKind::Invalid => {
}
}
}
CommandBufferBuilderKind::Invalid => {
}
}
}
// Step through the dependencies for this builder and add them to the finalized // render task root(s) for this surface match builder.kind {
CommandBufferBuilderKind::Tiled { ref tiles } => { for (_, descriptor) in tiles { for task_id in &builder.extra_dependencies {
rg_builder.add_dependency(
descriptor.current_task_id,
*task_id,
);
}
}
}
CommandBufferBuilderKind::Simple { render_task_id, .. } => { for task_id in &builder.extra_dependencies {
rg_builder.add_dependency(
render_task_id,
*task_id,
);
}
}
CommandBufferBuilderKind::Invalid { .. } => {}
}
// Set up the cmd-buffer targets to write prims into the popped surface self.current_cmd_buffers.init( self.builder_stack.last().unwrap_or(&CommandBufferBuilder::empty()), rg_builder
);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.