webrender/surface.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use api::units::*;
6use crate::command_buffer::{CommandBufferBuilderKind, CommandBufferList, CommandBufferBuilder, CommandBufferIndex};
7use crate::internal_types::FastHashMap;
8use crate::picture::{SurfaceInfo, SurfaceIndex, TileKey, SubSliceIndex, MAX_COMPOSITOR_SURFACES};
9use crate::prim_store::PictureIndex;
10use crate::render_task_graph::{RenderTaskId, RenderTaskGraphBuilder};
11use crate::render_target::ResolveOp;
12use crate::render_task::{RenderTask, RenderTaskKind, RenderTaskLocation};
13use crate::visibility::{VisibilityState, PrimitiveVisibility};
14
15/*
16 Contains functionality to help building the render task graph from a series of off-screen
17 surfaces that are created during the prepare pass. For now, it maintains existing behavior.
18 A future patch will add support for surface sub-graphs, while ensuring the render task
19 graph itself is built correctly with dependencies regardless of the surface kind (chained,
20 tiled, simple).
21 */
22
23// Information about the render task(s) for a given tile
24#[cfg_attr(feature = "capture", derive(Serialize))]
25#[cfg_attr(feature = "replay", derive(Deserialize))]
26pub struct SurfaceTileDescriptor {
27 /// Target render task for commands added to this tile. This is changed
28 /// each time a sub-graph is encountered on this tile
29 pub current_task_id: RenderTaskId,
30 /// The compositing task for this tile, if required. This is only needed
31 /// when a tile contains one or more sub-graphs.
32 pub composite_task_id: Option<RenderTaskId>,
33 /// Dirty rect for this tile
34 pub dirty_rect: PictureRect,
35}
36
37// Details of how a surface is rendered
38pub enum SurfaceDescriptorKind {
39 // Picture cache tiles
40 Tiled {
41 tiles: FastHashMap<TileKey, SurfaceTileDescriptor>,
42 },
43 // A single surface (e.g. for an opacity filter)
44 Simple {
45 render_task_id: RenderTaskId,
46 dirty_rect: PictureRect,
47 },
48 // A surface with 1+ intermediate tasks (e.g. blur)
49 Chained {
50 render_task_id: RenderTaskId,
51 root_task_id: RenderTaskId,
52 dirty_rect: PictureRect,
53 },
54}
55
56// Describes how a surface is rendered
57pub struct SurfaceDescriptor {
58 kind: SurfaceDescriptorKind,
59}
60
61impl SurfaceDescriptor {
62 // Create a picture cache tiled surface
63 pub fn new_tiled(
64 tiles: FastHashMap<TileKey, SurfaceTileDescriptor>,
65 ) -> Self {
66 SurfaceDescriptor {
67 kind: SurfaceDescriptorKind::Tiled {
68 tiles,
69 },
70 }
71 }
72
73 // Create a chained surface (e.g. blur)
74 pub fn new_chained(
75 render_task_id: RenderTaskId,
76 root_task_id: RenderTaskId,
77 dirty_rect: PictureRect,
78 ) -> Self {
79 SurfaceDescriptor {
80 kind: SurfaceDescriptorKind::Chained {
81 render_task_id,
82 root_task_id,
83 dirty_rect,
84 },
85 }
86 }
87
88 // Create a simple surface (e.g. opacity)
89 pub fn new_simple(
90 render_task_id: RenderTaskId,
91 dirty_rect: PictureRect,
92 ) -> Self {
93 SurfaceDescriptor {
94 kind: SurfaceDescriptorKind::Simple {
95 render_task_id,
96 dirty_rect,
97 },
98 }
99 }
100}
101
102// Describes a list of command buffers that we are adding primitives to
103// for a given surface. These are created from a command buffer builder
104// as an optimization - skipping the indirection pic_task -> cmd_buffer_index
105struct CommandBufferTargets {
106 available_cmd_buffers: Vec<Vec<(PictureRect, CommandBufferIndex)>>,
107}
108
109impl CommandBufferTargets {
110 fn new() -> Self {
111 CommandBufferTargets {
112 available_cmd_buffers: vec![Vec::new(); MAX_COMPOSITOR_SURFACES+1],
113 }
114 }
115
116 fn init(
117 &mut self,
118 cb: &CommandBufferBuilder,
119 rg_builder: &RenderTaskGraphBuilder,
120 ) {
121 for available_cmd_buffers in &mut self.available_cmd_buffers {
122 available_cmd_buffers.clear();
123 }
124
125 match cb.kind {
126 CommandBufferBuilderKind::Tiled { ref tiles, .. } => {
127 for (key, desc) in tiles {
128 let task = rg_builder.get_task(desc.current_task_id);
129 match task.kind {
130 RenderTaskKind::Picture(ref info) => {
131 let available_cmd_buffers = &mut self.available_cmd_buffers[key.sub_slice_index.as_usize()];
132 available_cmd_buffers.push((desc.dirty_rect, info.cmd_buffer_index));
133 }
134 _ => unreachable!("bug: not a picture"),
135 }
136 }
137 }
138 CommandBufferBuilderKind::Simple { render_task_id, dirty_rect, .. } => {
139 let task = rg_builder.get_task(render_task_id);
140 match task.kind {
141 RenderTaskKind::Picture(ref info) => {
142 for sub_slice_buffer in &mut self.available_cmd_buffers {
143 sub_slice_buffer.push((dirty_rect, info.cmd_buffer_index));
144 }
145 }
146 _ => unreachable!("bug: not a picture"),
147 }
148 }
149 CommandBufferBuilderKind::Invalid => {}
150 };
151 }
152
153 /// For a given rect and sub-slice, get a list of command buffers to write commands to
154 fn get_cmd_buffer_targets_for_rect(
155 &mut self,
156 rect: &PictureRect,
157 sub_slice_index: SubSliceIndex,
158 targets: &mut Vec<CommandBufferIndex>,
159 ) -> bool {
160
161 for (dirty_rect, cmd_buffer_index) in &self.available_cmd_buffers[sub_slice_index.as_usize()] {
162 if dirty_rect.intersects(rect) {
163 targets.push(*cmd_buffer_index);
164 }
165 }
166
167 !targets.is_empty()
168 }
169}
170
171// Main helper interface to build a graph of surfaces. In future patches this
172// will support building sub-graphs.
173pub struct SurfaceBuilder {
174 // The currently set cmd buffer targets (updated during push/pop)
175 current_cmd_buffers: CommandBufferTargets,
176 // Stack of surfaces that are parents to the current targets
177 builder_stack: Vec<CommandBufferBuilder>,
178 // A map of the output render tasks from any sub-graphs that haven't
179 // been consumed by BackdropRender prims yet
180 pub sub_graph_output_map: FastHashMap<PictureIndex, RenderTaskId>,
181}
182
183impl SurfaceBuilder {
184 pub fn new() -> Self {
185 SurfaceBuilder {
186 current_cmd_buffers: CommandBufferTargets::new(),
187 builder_stack: Vec::new(),
188 sub_graph_output_map: FastHashMap::default(),
189 }
190 }
191
192 /// Register the current surface as the source of a resolve for the task sub-graph that
193 /// is currently on the surface builder stack.
194 pub fn register_resolve_source(
195 &mut self,
196 ) {
197 let surface_task_id = match self.builder_stack.last().unwrap().kind {
198 CommandBufferBuilderKind::Tiled { .. } | CommandBufferBuilderKind::Invalid => {
199 panic!("bug: only supported for non-tiled surfaces");
200 }
201 CommandBufferBuilderKind::Simple { render_task_id, .. } => render_task_id,
202 };
203
204 for builder in self.builder_stack.iter_mut().rev() {
205 if builder.establishes_sub_graph {
206 assert_eq!(builder.resolve_source, None);
207 builder.resolve_source = Some(surface_task_id);
208 return;
209 }
210 }
211
212 unreachable!("bug: resolve source with no sub-graph");
213 }
214
215 pub fn push_surface(
216 &mut self,
217 surface_index: SurfaceIndex,
218 is_sub_graph: bool,
219 clipping_rect: PictureRect,
220 descriptor: Option<SurfaceDescriptor>,
221 surfaces: &mut [SurfaceInfo],
222 rg_builder: &RenderTaskGraphBuilder,
223 ) {
224 // Init the surface
225 surfaces[surface_index.0].clipping_rect = clipping_rect;
226
227 let builder = if let Some(descriptor) = descriptor {
228 match descriptor.kind {
229 SurfaceDescriptorKind::Tiled { tiles } => {
230 CommandBufferBuilder::new_tiled(
231 tiles,
232 )
233 }
234 SurfaceDescriptorKind::Simple { render_task_id, dirty_rect, .. } => {
235 CommandBufferBuilder::new_simple(
236 render_task_id,
237 is_sub_graph,
238 None,
239 dirty_rect,
240 )
241 }
242 SurfaceDescriptorKind::Chained { render_task_id, root_task_id, dirty_rect, .. } => {
243 CommandBufferBuilder::new_simple(
244 render_task_id,
245 is_sub_graph,
246 Some(root_task_id),
247 dirty_rect,
248 )
249 }
250 }
251 } else {
252 CommandBufferBuilder::empty()
253 };
254
255 self.current_cmd_buffers.init(&builder, rg_builder);
256 self.builder_stack.push(builder);
257 }
258
259 // Add a child render task (e.g. a render task cache item, or a clip mask) as a
260 // dependency of the current surface
261 pub fn add_child_render_task(
262 &mut self,
263 child_task_id: RenderTaskId,
264 rg_builder: &mut RenderTaskGraphBuilder,
265 ) {
266 let builder = self.builder_stack.last().unwrap();
267
268 match builder.kind {
269 CommandBufferBuilderKind::Tiled { ref tiles } => {
270 for (_, descriptor) in tiles {
271 rg_builder.add_dependency(
272 descriptor.current_task_id,
273 child_task_id,
274 );
275 }
276 }
277 CommandBufferBuilderKind::Simple { render_task_id, .. } => {
278 rg_builder.add_dependency(
279 render_task_id,
280 child_task_id,
281 );
282 }
283 CommandBufferBuilderKind::Invalid { .. } => {}
284 }
285 }
286
287 // Add a picture render task as a dependency of the parent surface. This is a
288 // special case with extra complexity as the root of the surface may change
289 // when inside a sub-graph. It's currently only needed for drop-shadow effects.
290 pub fn add_picture_render_task(
291 &mut self,
292 child_task_id: RenderTaskId,
293 ) {
294 self.builder_stack
295 .last_mut()
296 .unwrap()
297 .extra_dependencies
298 .push(child_task_id);
299 }
300
301 // Get a list of command buffer indices that primitives should be pushed
302 // to for a given current visbility / dirty state
303 pub fn get_cmd_buffer_targets_for_prim(
304 &mut self,
305 vis: &PrimitiveVisibility,
306 targets: &mut Vec<CommandBufferIndex>,
307 ) -> bool {
308 targets.clear();
309
310 match vis.state {
311 VisibilityState::Unset => {
312 panic!("bug: invalid vis state");
313 }
314 VisibilityState::Culled => {
315 false
316 }
317 VisibilityState::Visible { sub_slice_index, .. } => {
318 self.current_cmd_buffers.get_cmd_buffer_targets_for_rect(
319 &vis.clip_chain.pic_coverage_rect,
320 sub_slice_index,
321 targets,
322 )
323 }
324 VisibilityState::PassThrough => {
325 true
326 }
327 }
328 }
329
330 pub fn pop_empty_surface(&mut self) {
331 let builder = self.builder_stack.pop().unwrap();
332 assert!(!builder.establishes_sub_graph);
333 }
334
335 // Finish adding primitives and child tasks to a surface and pop it off the stack
336 pub fn pop_surface(
337 &mut self,
338 pic_index: PictureIndex,
339 rg_builder: &mut RenderTaskGraphBuilder,
340 cmd_buffers: &mut CommandBufferList,
341 ) {
342 let builder = self.builder_stack.pop().unwrap();
343
344 if builder.establishes_sub_graph {
345 // If we are popping a sub-graph off the stack the dependency setup is rather more complex...
346 match builder.kind {
347 CommandBufferBuilderKind::Tiled { .. } | CommandBufferBuilderKind::Invalid => {
348 unreachable!("bug: sub-graphs can only be simple surfaces");
349 }
350 CommandBufferBuilderKind::Simple { render_task_id: child_render_task_id, root_task_id: child_root_task_id, .. } => {
351 // Get info about the resolve operation to copy from parent surface or tiles to the picture cache task
352 if let Some(resolve_task_id) = builder.resolve_source {
353 let mut src_task_ids = Vec::new();
354
355 // Make the output of the sub-graph a dependency of the new replacement tile task
356 let _old = self.sub_graph_output_map.insert(
357 pic_index,
358 child_root_task_id.unwrap_or(child_render_task_id),
359 );
360 debug_assert!(_old.is_none());
361
362 // Set up dependencies for the sub-graph. The basic concepts below are the same, but for
363 // tiled surfaces are a little more complex as there are multiple tasks to set up.
364 // (a) Set up new task(s) on parent surface that write to the same location
365 // (b) Set up a resolve target to copy from parent surface tasks(s) to the resolve target
366 // (c) Make the old parent surface tasks input dependencies of the resolve target
367 // (d) Make the sub-graph output an input dependency of the new task(s).
368
369 match self.builder_stack.last_mut().unwrap().kind {
370 CommandBufferBuilderKind::Tiled { ref mut tiles } => {
371 let keys: Vec<TileKey> = tiles.keys().cloned().collect();
372
373 // For each tile in parent surface
374 for key in keys {
375 let descriptor = tiles.remove(&key).unwrap();
376 let parent_task_id = descriptor.current_task_id;
377 let parent_task = rg_builder.get_task_mut(parent_task_id);
378
379 match parent_task.location {
380 RenderTaskLocation::Unallocated { .. } | RenderTaskLocation::Existing { .. } => {
381 // Get info about the parent tile task location and params
382 let location = RenderTaskLocation::Existing {
383 parent_task_id,
384 size: parent_task.location.size(),
385 };
386
387 let pic_task = match parent_task.kind {
388 RenderTaskKind::Picture(ref mut pic_task) => {
389 let cmd_buffer_index = cmd_buffers.create_cmd_buffer();
390 let new_pic_task = pic_task.duplicate(cmd_buffer_index);
391
392 // Add the resolve src to copy from tile -> picture input task
393 src_task_ids.push(parent_task_id);
394
395 new_pic_task
396 }
397 _ => panic!("bug: not a picture"),
398 };
399
400 // Make the existing tile an input dependency of the resolve target
401 rg_builder.add_dependency(
402 resolve_task_id,
403 parent_task_id,
404 );
405
406 // Create the new task to replace the tile task
407 let new_task_id = rg_builder.add().init(
408 RenderTask::new(
409 location, // draw to same place
410 RenderTaskKind::Picture(pic_task),
411 ),
412 );
413
414 // Ensure that the parent task will get scheduled earlier during
415 // pass assignment since we are reusing the existing surface,
416 // even though it's not technically needed for rendering order.
417 rg_builder.add_dependency(
418 new_task_id,
419 parent_task_id,
420 );
421
422 // Update the surface builder with the now current target for future primitives
423 tiles.insert(
424 key,
425 SurfaceTileDescriptor {
426 current_task_id: new_task_id,
427 ..descriptor
428 },
429 );
430 }
431 RenderTaskLocation::Static { .. } => {
432 // Update the surface builder with the now current target for future primitives
433 tiles.insert(
434 key,
435 descriptor,
436 );
437 }
438 _ => {
439 panic!("bug: unexpected task location");
440 }
441 }
442 }
443 }
444 CommandBufferBuilderKind::Simple { render_task_id: ref mut parent_task_id, .. } => {
445 let parent_task = rg_builder.get_task_mut(*parent_task_id);
446
447 // Get info about the parent tile task location and params
448 let location = RenderTaskLocation::Existing {
449 parent_task_id: *parent_task_id,
450 size: parent_task.location.size(),
451 };
452 let pic_task = match parent_task.kind {
453 RenderTaskKind::Picture(ref mut pic_task) => {
454 let cmd_buffer_index = cmd_buffers.create_cmd_buffer();
455
456 let new_pic_task = pic_task.duplicate(cmd_buffer_index);
457
458 // Add the resolve src to copy from tile -> picture input task
459 src_task_ids.push(*parent_task_id);
460
461 new_pic_task
462 }
463 _ => panic!("bug: not a picture"),
464 };
465
466 // Make the existing surface an input dependency of the resolve target
467 rg_builder.add_dependency(
468 resolve_task_id,
469 *parent_task_id,
470 );
471
472 // Create the new task to replace the parent surface task
473 let new_task_id = rg_builder.add().init(
474 RenderTask::new(
475 location, // draw to same place
476 RenderTaskKind::Picture(pic_task),
477 ),
478 );
479
480 // Ensure that the parent task will get scheduled earlier during
481 // pass assignment since we are reusing the existing surface,
482 // even though it's not technically needed for rendering order.
483 rg_builder.add_dependency(
484 new_task_id,
485 *parent_task_id,
486 );
487
488 // Update the surface builder with the now current target for future primitives
489 *parent_task_id = new_task_id;
490 }
491 CommandBufferBuilderKind::Invalid => {
492 unreachable!();
493 }
494 }
495
496 let dest_task = rg_builder.get_task_mut(resolve_task_id);
497
498 match dest_task.kind {
499 RenderTaskKind::Picture(ref mut dest_task_info) => {
500 assert!(dest_task_info.resolve_op.is_none());
501 dest_task_info.resolve_op = Some(ResolveOp {
502 src_task_ids,
503 dest_task_id: resolve_task_id,
504 })
505 }
506 _ => {
507 unreachable!("bug: not a picture");
508 }
509 }
510 }
511
512 // This can occur if there is an edge case where the resolve target is found
513 // not visible even though the filter chain was (for example, in the case of
514 // an extreme scale causing floating point inaccuracies). Adding a dependency
515 // here is also a safety in case for some reason the backdrop render primitive
516 // doesn't pick up the dependency, ensuring that it gets scheduled and freed
517 // as early as possible.
518 match self.builder_stack.last().unwrap().kind {
519 CommandBufferBuilderKind::Tiled { ref tiles } => {
520 // For a tiled render task, add as a dependency to every tile.
521 for (_, descriptor) in tiles {
522 rg_builder.add_dependency(
523 descriptor.current_task_id,
524 child_root_task_id.unwrap_or(child_render_task_id),
525 );
526 }
527 }
528 CommandBufferBuilderKind::Simple { render_task_id: parent_task_id, .. } => {
529 rg_builder.add_dependency(
530 parent_task_id,
531 child_root_task_id.unwrap_or(child_render_task_id),
532 );
533 }
534 CommandBufferBuilderKind::Invalid => {
535 unreachable!();
536 }
537 }
538 }
539 }
540 } else {
541 match builder.kind {
542 CommandBufferBuilderKind::Tiled { ref tiles } => {
543 for (_, descriptor) in tiles {
544 if let Some(composite_task_id) = descriptor.composite_task_id {
545 rg_builder.add_dependency(
546 composite_task_id,
547 descriptor.current_task_id,
548 );
549
550 let composite_task = rg_builder.get_task_mut(composite_task_id);
551 match composite_task.kind {
552 RenderTaskKind::TileComposite(ref mut info) => {
553 info.task_id = Some(descriptor.current_task_id);
554 }
555 _ => unreachable!("bug: not a tile composite"),
556 }
557 }
558 }
559 }
560 CommandBufferBuilderKind::Simple { render_task_id: child_task_id, root_task_id: child_root_task_id, .. } => {
561 match self.builder_stack.last().unwrap().kind {
562 CommandBufferBuilderKind::Tiled { ref tiles } => {
563 // For a tiled render task, add as a dependency to every tile.
564 for (_, descriptor) in tiles {
565 rg_builder.add_dependency(
566 descriptor.current_task_id,
567 child_root_task_id.unwrap_or(child_task_id),
568 );
569 }
570 }
571 CommandBufferBuilderKind::Simple { render_task_id: parent_task_id, .. } => {
572 rg_builder.add_dependency(
573 parent_task_id,
574 child_root_task_id.unwrap_or(child_task_id),
575 );
576 }
577 CommandBufferBuilderKind::Invalid => {
578 }
579 }
580 }
581 CommandBufferBuilderKind::Invalid => {
582 }
583 }
584 }
585
586 // Step through the dependencies for this builder and add them to the finalized
587 // render task root(s) for this surface
588 match builder.kind {
589 CommandBufferBuilderKind::Tiled { ref tiles } => {
590 for (_, descriptor) in tiles {
591 for task_id in &builder.extra_dependencies {
592 rg_builder.add_dependency(
593 descriptor.current_task_id,
594 *task_id,
595 );
596 }
597 }
598 }
599 CommandBufferBuilderKind::Simple { render_task_id, .. } => {
600 for task_id in &builder.extra_dependencies {
601 rg_builder.add_dependency(
602 render_task_id,
603 *task_id,
604 );
605 }
606 }
607 CommandBufferBuilderKind::Invalid { .. } => {}
608 }
609
610 // Set up the cmd-buffer targets to write prims into the popped surface
611 self.current_cmd_buffers.init(
612 self.builder_stack.last().unwrap_or(&CommandBufferBuilder::empty()), rg_builder
613 );
614 }
615
616 pub fn finalize(self) {
617 assert!(self.builder_stack.is_empty());
618 }
619}