summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/src/surface.rs
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wr/webrender/src/surface.rs')
-rw-r--r--gfx/wr/webrender/src/surface.rs665
1 files changed, 665 insertions, 0 deletions
diff --git a/gfx/wr/webrender/src/surface.rs b/gfx/wr/webrender/src/surface.rs
new file mode 100644
index 0000000000..20c0f2fa9b
--- /dev/null
+++ b/gfx/wr/webrender/src/surface.rs
@@ -0,0 +1,665 @@
+/* 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 api::units::*;
+use crate::command_buffer::{CommandBufferBuilderKind, CommandBufferList, CommandBufferBuilder, CommandBufferIndex, PrimitiveCommand};
+use crate::internal_types::FastHashMap;
+use crate::picture::{SurfaceInfo, SurfaceIndex, TileKey, SubSliceIndex};
+use crate::prim_store::{PictureIndex};
+use crate::render_task_graph::{RenderTaskId, RenderTaskGraphBuilder};
+use crate::spatial_tree::SpatialNodeIndex;
+use crate::render_target::ResolveOp;
+use crate::render_task::{RenderTask, RenderTaskKind, RenderTaskLocation};
+use crate::visibility::{VisibilityState, PrimitiveVisibility};
+
+/*
+ Contains functionality to help building the render task graph from a series of off-screen
+ surfaces that are created during the prepare pass. For now, it maintains existing behavior.
+ A future patch will add support for surface sub-graphs, while ensuring the render task
+ graph itself is built correctly with dependencies regardless of the surface kind (chained,
+ tiled, simple).
+ */
+
+// 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>,
+}
+
+// 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,
+ },
+ // A surface with 1+ intermediate tasks (e.g. blur)
+ Chained {
+ render_task_id: RenderTaskId,
+ root_task_id: RenderTaskId,
+ },
+}
+
+// Describes how a surface is rendered
+pub struct SurfaceDescriptor {
+ kind: SurfaceDescriptorKind,
+ dirty_rects: Vec<PictureRect>,
+}
+
+impl SurfaceDescriptor {
+ // Create a picture cache tiled surface
+ pub fn new_tiled(
+ tiles: FastHashMap<TileKey, SurfaceTileDescriptor>,
+ dirty_rects: Vec<PictureRect>,
+ ) -> Self {
+ SurfaceDescriptor {
+ kind: SurfaceDescriptorKind::Tiled {
+ tiles,
+ },
+ dirty_rects,
+ }
+ }
+
+ // 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_rects: vec![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_rects: vec![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
+enum CommandBufferTargets {
+ // Picture cache targets target multiple command buffers
+ Tiled {
+ tiles: FastHashMap<TileKey, CommandBufferIndex>,
+ },
+ // Child surfaces target a single command buffer
+ Simple {
+ cmd_buffer_index: CommandBufferIndex,
+ },
+}
+
+impl CommandBufferTargets {
+ // Initialize command buffer targets from a command buffer builder
+ fn init(
+ &mut self,
+ cb: &CommandBufferBuilder,
+ rg_builder: &RenderTaskGraphBuilder,
+ ) {
+ let new_target = match cb.kind {
+ CommandBufferBuilderKind::Tiled { ref tiles, .. } => {
+ let mut cb_tiles = FastHashMap::default();
+
+ for (key, desc) in tiles {
+ let task = rg_builder.get_task(desc.current_task_id);
+ match task.kind {
+ RenderTaskKind::Picture(ref info) => {
+ cb_tiles.insert(*key, info.cmd_buffer_index);
+ }
+ _ => unreachable!("bug: not a picture"),
+ }
+ }
+
+ CommandBufferTargets::Tiled { tiles: cb_tiles }
+ }
+ CommandBufferBuilderKind::Simple { render_task_id, .. } => {
+ let task = rg_builder.get_task(render_task_id);
+ match task.kind {
+ RenderTaskKind::Picture(ref info) => {
+ CommandBufferTargets::Simple { cmd_buffer_index: info.cmd_buffer_index }
+ }
+ _ => unreachable!("bug: not a picture"),
+ }
+ }
+ CommandBufferBuilderKind::Invalid => {
+ CommandBufferTargets::Tiled { tiles: FastHashMap::default() }
+ }
+ };
+
+ *self = new_target;
+ }
+
+ /// Push a new primitive in to the command buffer builder
+ fn push_prim(
+ &mut self,
+ prim_cmd: &PrimitiveCommand,
+ spatial_node_index: SpatialNodeIndex,
+ tile_rect: crate::picture::TileRect,
+ sub_slice_index: SubSliceIndex,
+ cmd_buffers: &mut CommandBufferList,
+ ) {
+ match self {
+ CommandBufferTargets::Tiled { ref mut tiles } => {
+ // For tiled builders, add the prim to the command buffer of each
+ // tile that this primitive affects.
+ for y in tile_rect.min.y .. tile_rect.max.y {
+ for x in tile_rect.min.x .. tile_rect.max.x {
+ let key = TileKey {
+ tile_offset: crate::picture::TileOffset::new(x, y),
+ sub_slice_index,
+ };
+ if let Some(cmd_buffer_index) = tiles.get(&key) {
+ cmd_buffers.get_mut(*cmd_buffer_index).add_prim(
+ prim_cmd,
+ spatial_node_index,
+ );
+ }
+ }
+ }
+ }
+ CommandBufferTargets::Simple { cmd_buffer_index, .. } => {
+ // For simple builders, just add the prim
+ cmd_buffers.get_mut(*cmd_buffer_index).add_prim(
+ prim_cmd,
+ spatial_node_index,
+ );
+ }
+ }
+ }
+}
+
+// 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>,
+ // Dirty rect stack used to reject adding primitives
+ dirty_rect_stack: Vec<Vec<PictureRect>>,
+ // 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::Tiled { tiles: FastHashMap::default() },
+ builder_stack: Vec::new(),
+ dirty_rect_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: SurfaceDescriptor,
+ surfaces: &mut [SurfaceInfo],
+ rg_builder: &RenderTaskGraphBuilder,
+ ) {
+ // Init the surface
+ surfaces[surface_index.0].clipping_rect = clipping_rect;
+
+ self.dirty_rect_stack.push(descriptor.dirty_rects);
+
+ let builder = match descriptor.kind {
+ SurfaceDescriptorKind::Tiled { tiles } => {
+ CommandBufferBuilder::new_tiled(
+ tiles,
+ )
+ }
+ SurfaceDescriptorKind::Simple { render_task_id } => {
+ CommandBufferBuilder::new_simple(
+ render_task_id,
+ is_sub_graph,
+ None,
+ )
+ }
+ SurfaceDescriptorKind::Chained { render_task_id, root_task_id } => {
+ CommandBufferBuilder::new_simple(
+ render_task_id,
+ is_sub_graph,
+ Some(root_task_id),
+ )
+ }
+ };
+
+ 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);
+ }
+
+ // Returns true if the given primitive is visible and also intersects the dirty
+ // region of the current surface
+ pub fn is_prim_visible_and_in_dirty_region(
+ &self,
+ vis: &PrimitiveVisibility,
+ ) -> bool {
+ match vis.state {
+ VisibilityState::Unset => {
+ panic!("bug: invalid vis state");
+ }
+ VisibilityState::Culled => {
+ false
+ }
+ VisibilityState::Visible { .. } => {
+ self.dirty_rect_stack
+ .last()
+ .unwrap()
+ .iter()
+ .any(|dirty_rect| {
+ dirty_rect.intersects(&vis.clip_chain.pic_coverage_rect)
+ })
+ }
+ VisibilityState::PassThrough => {
+ true
+ }
+ }
+ }
+
+ // Push a primitive to the current cmd buffer target(s)
+ pub fn push_prim(
+ &mut self,
+ prim_cmd: &PrimitiveCommand,
+ spatial_node_index: SpatialNodeIndex,
+ vis: &PrimitiveVisibility,
+ cmd_buffers: &mut CommandBufferList,
+ ) {
+ match vis.state {
+ VisibilityState::Unset => {
+ panic!("bug: invalid vis state");
+ }
+ VisibilityState::Visible { tile_rect, sub_slice_index, .. } => {
+ self.current_cmd_buffers.push_prim(
+ prim_cmd,
+ spatial_node_index,
+ tile_rect,
+ sub_slice_index,
+ cmd_buffers,
+ )
+ }
+ VisibilityState::PassThrough | VisibilityState::Culled => {}
+ }
+ }
+
+ // 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,
+ ) {
+ self.dirty_rect_stack.pop().unwrap();
+
+ 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, .. } => {
+ 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,
+ );
+
+ // 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 => {
+ unreachable!();
+ }
+ }
+ }
+ CommandBufferBuilderKind::Invalid => {
+ unreachable!();
+ }
+ }
+ }
+
+ // 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());
+ }
+}