- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 93 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 93 %
- : 95 %
- : 95 %
- : 95 %
- : 93 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 93 %
- : 93 %
- : 95 %
- : 95 %
- : 93 %
- : 95 %
- : 93 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 84 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 84 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 93 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 94 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
Source code
Revision control
Copy as Markdown
Other Tools
/* 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/. */
//! 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.
use api::units::*;
use crate::box_shadow::BLUR_SAMPLE_SCALE;
use crate::command_buffer::{CommandBufferBuilderKind, CommandBufferList, CommandBufferBuilder, CommandBufferIndex};
use crate::internal_types::{FastHashMap, Filter};
use crate::picture::PictureCompositeMode;
use crate::tile_cache::{TileKey, SubSliceIndex, MAX_COMPOSITOR_SURFACES};
use crate::prim_store::PictureIndex;
use crate::render_task_graph::{RenderTaskId, RenderTaskGraphBuilder};
use crate::render_target::ResolveOp;
use crate::render_task::{RenderTask, RenderTaskKind, RenderTaskLocation};
use crate::space::SpaceMapper;
use crate::spatial_tree::{SpatialTree, SpatialNodeIndex};
use crate::util::MaxRect;
use crate::visibility::{VisibilityState, PrimitiveVisibility, FrameVisibilityContext};
pub use crate::picture_composite_mode::get_surface_rects;
/// 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))]
pub struct SurfaceIndex(pub usize);
/// Specify whether a surface allows subpixel AA text rendering.
#[derive(Debug, Copy, Clone)]
pub enum 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.
pub struct 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.
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,
/// If true, allow snapping on this and child surfaces
pub allow_snapping: bool,
/// If true, the scissor rect must be set when drawing this surface
pub force_scissor_rect: bool,
}
impl SurfaceInfo {
pub fn new(
surface_spatial_node_index: SpatialNodeIndex,
raster_spatial_node_index: SpatialNodeIndex,
world_rect: WorldRect,
spatial_tree: &SpatialTree,
device_pixel_scale: DevicePixelScale,
world_scale_factors: (f32, f32),
local_scale: (f32, f32),
allow_snapping: bool,
force_scissor_rect: bool,
) -> Self {
let map_surface_to_world = SpaceMapper::new_with_target(
spatial_tree.root_reference_frame_index(),
surface_spatial_node_index,
world_rect,
spatial_tree,
);
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.
pub fn 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;
let largest_scaled_blur_radius = f32::max(
sx_blur_radius * self.world_scale_factors.0,
sy_blur_radius * self.world_scale_factors.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)
}
}
pub fn update_culling_rect(
&mut self,
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;
if let 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.
if let 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
if let Some(rect) = map_surface_to_vis.map(&expanded_rect) {
self.culling_rect = rect;
}
}
}
}
}
pub fn map_to_device_rect(
&self,
picture_rect: &PictureRect,
spatial_tree: &SpatialTree,
) -> DeviceRect {
let raster_rect = if self.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 `PicturePrimitive::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,
);
pic_to_raster.map(&picture_rect).unwrap()
} else {
picture_rect.cast_unit()
};
raster_rect * self.device_pixel_scale
}
/// 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)
pub fn 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 = if self.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))]
pub struct 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
pub enum 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
pub struct SurfaceDescriptor {
kind: SurfaceDescriptorKind,
}
impl SurfaceDescriptor {
// Create a picture cache tiled surface
pub fn new_tiled(
tiles: FastHashMap<TileKey, SurfaceTileDescriptor>,
) -> Self {
SurfaceDescriptor {
kind: SurfaceDescriptorKind::Tiled {
tiles,
},
}
}
// Create a chained surface (e.g. blur)
pub fn new_chained(
render_task_id: RenderTaskId,
root_task_id: RenderTaskId,
dirty_rect: PictureRect,
) -> Self {
SurfaceDescriptor {
kind: SurfaceDescriptorKind::Chained {
render_task_id,
root_task_id,
dirty_rect,
},
}
}
// Create a simple surface (e.g. opacity)
pub fn new_simple(
render_task_id: RenderTaskId,
dirty_rect: PictureRect,
) -> Self {
SurfaceDescriptor {
kind: SurfaceDescriptorKind::Simple {
render_task_id,
dirty_rect,
},
}
}
}
// 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)>>,
}
impl CommandBufferTargets {
fn new() -> Self {
CommandBufferTargets {
available_cmd_buffers: vec![Vec::new(); MAX_COMPOSITOR_SURFACES+1],
}
}
fn init(
&mut self,
cb: &CommandBufferBuilder,
rg_builder: &RenderTaskGraphBuilder,
) {
for available_cmd_buffers in &mut self.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 = &mut self.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 &mut self.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(
&mut self,
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.
pub struct 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>,
}
impl SurfaceBuilder {
pub fn new() -> Self {
SurfaceBuilder {
current_cmd_buffers: CommandBufferTargets::new(),
builder_stack: Vec::new(),
sub_graph_output_map: FastHashMap::default(),
}
}
/// Register the current surface as the source of a resolve for the task sub-graph that
/// is currently on the surface builder stack.
pub fn register_resolve_source(
&mut self,
) {
let surface_task_id = match self.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 in self.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");
}
pub fn push_surface(
&mut self,
surface_index: SurfaceIndex,
is_sub_graph: bool,
clipping_rect: PictureRect,
descriptor: Option<SurfaceDescriptor>,
surfaces: &mut [SurfaceInfo],
rg_builder: &RenderTaskGraphBuilder,
) {
// Init the surface
surfaces[surface_index.0].clipping_rect = clipping_rect;
let builder = if let Some(descriptor) = descriptor {
match descriptor.kind {
SurfaceDescriptorKind::Tiled { tiles } => {
CommandBufferBuilder::new_tiled(
tiles,
)
}
SurfaceDescriptorKind::Simple { render_task_id, dirty_rect, .. } => {
CommandBufferBuilder::new_simple(
render_task_id,
is_sub_graph,
None,
dirty_rect,
)
}
SurfaceDescriptorKind::Chained { render_task_id, root_task_id, dirty_rect, .. } => {
CommandBufferBuilder::new_simple(
render_task_id,
is_sub_graph,
Some(root_task_id),
dirty_rect,
)
}
}
} else {
CommandBufferBuilder::empty()
};
self.current_cmd_buffers.init(&builder, rg_builder);
self.builder_stack.push(builder);
}
// Add a child render task (e.g. a render task cache item, or a clip mask) as a
// dependency of the current surface
pub fn add_child_render_task(
&mut self,
child_task_id: RenderTaskId,
rg_builder: &mut RenderTaskGraphBuilder,
) {
let builder = self.builder_stack.last().unwrap();
match builder.kind {
CommandBufferBuilderKind::Tiled { ref tiles } => {
for (_, descriptor) in tiles {
rg_builder.add_dependency(
descriptor.current_task_id,
child_task_id,
);
}
}
CommandBufferBuilderKind::Simple { render_task_id, .. } => {
rg_builder.add_dependency(
render_task_id,
child_task_id,
);
}
CommandBufferBuilderKind::Invalid { .. } => {}
}
}
// 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.
pub fn add_picture_render_task(
&mut self,
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
pub fn get_cmd_buffer_targets_for_prim(
&mut self,
vis: &PrimitiveVisibility,
targets: &mut Vec<CommandBufferIndex>,
) -> bool {
targets.clear();
match vis.state {
VisibilityState::Unset => {
panic!("bug: invalid vis state");
}
VisibilityState::Culled => {
false
}
VisibilityState::Visible { sub_slice_index, .. } => {
self.current_cmd_buffers.get_cmd_buffer_targets_for_rect(
&vis.clip_chain.pic_coverage_rect,
sub_slice_index,
targets,
)
}
VisibilityState::PassThrough => {
true
}
}
}
pub fn pop_empty_surface(&mut self) {
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
pub fn pop_surface(
&mut self,
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
if let Some(resolve_task_id) = builder.resolve_source {
let mut 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).
match self.builder_stack.last_mut().unwrap().kind {
CommandBufferBuilderKind::Tiled { ref mut tiles } => {
let keys: Vec<TileKey> = tiles.keys().cloned().collect();
// 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(ref mut 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: ref mut 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(ref mut 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.
if let 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(ref mut 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.
match self.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 {
if let 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(ref mut 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, .. } => {
match self.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
);
}
pub fn finalize(self) {
assert!(self.builder_stack.is_empty());
}
}
pub fn calculate_screen_uv(
p: DevicePoint,
clipped: DeviceRect,
) -> DeviceHomogeneousVector {
// TODO(gw): Switch to a simple mix, no bilerp / homogeneous vec needed anymore
DeviceHomogeneousVector::new(
(p.x - clipped.min.x) / (clipped.max.x - clipped.min.x),
(p.y - clipped.min.y) / (clipped.max.y - clipped.min.y),
0.0,
1.0,
)
}