1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "cc/trees/occlusion_tracker.h"
9 #include "cc/base/math_util.h"
10 #include "cc/base/region.h"
11 #include "cc/layers/layer.h"
12 #include "cc/layers/layer_impl.h"
13 #include "cc/layers/render_surface.h"
14 #include "cc/layers/render_surface_impl.h"
15 #include "ui/gfx/geometry/quad_f.h"
16 #include "ui/gfx/geometry/rect_conversions.h"
20 template <typename LayerType
>
21 OcclusionTracker
<LayerType
>::OcclusionTracker(
22 const gfx::Rect
& screen_space_clip_rect
)
23 : screen_space_clip_rect_(screen_space_clip_rect
),
24 occluding_screen_space_rects_(NULL
),
25 non_occluding_screen_space_rects_(NULL
) {}
27 template <typename LayerType
>
28 OcclusionTracker
<LayerType
>::~OcclusionTracker() {}
30 template <typename LayerType
>
31 Occlusion OcclusionTracker
<LayerType
>::GetCurrentOcclusionForLayer(
32 const gfx::Transform
& draw_transform
) const {
33 DCHECK(!stack_
.empty());
34 const StackObject
& back
= stack_
.back();
35 return Occlusion(draw_transform
,
36 back
.occlusion_from_outside_target
,
37 back
.occlusion_from_inside_target
);
40 template <typename LayerType
>
41 void OcclusionTracker
<LayerType
>::EnterLayer(
42 const LayerIteratorPosition
<LayerType
>& layer_iterator
) {
43 LayerType
* render_target
= layer_iterator
.target_render_surface_layer
;
45 if (layer_iterator
.represents_itself
)
46 EnterRenderTarget(render_target
);
47 else if (layer_iterator
.represents_target_render_surface
)
48 FinishedRenderTarget(render_target
);
51 template <typename LayerType
>
52 void OcclusionTracker
<LayerType
>::LeaveLayer(
53 const LayerIteratorPosition
<LayerType
>& layer_iterator
) {
54 LayerType
* render_target
= layer_iterator
.target_render_surface_layer
;
56 if (layer_iterator
.represents_itself
)
57 MarkOccludedBehindLayer(layer_iterator
.current_layer
);
58 // TODO(danakj): This should be done when entering the contributing surface,
59 // but in a way that the surface's own occlusion won't occlude itself.
60 else if (layer_iterator
.represents_contributing_render_surface
)
61 LeaveToRenderTarget(render_target
);
64 template <typename RenderSurfaceType
>
65 static gfx::Rect
ScreenSpaceClipRectInTargetSurface(
66 const RenderSurfaceType
* target_surface
,
67 const gfx::Rect
& screen_space_clip_rect
) {
68 gfx::Transform
inverse_screen_space_transform(
69 gfx::Transform::kSkipInitialization
);
70 if (!target_surface
->screen_space_transform().GetInverse(
71 &inverse_screen_space_transform
))
72 return target_surface
->content_rect();
74 return MathUtil::ProjectEnclosingClippedRect(inverse_screen_space_transform
,
75 screen_space_clip_rect
);
78 template <typename RenderSurfaceType
>
79 static SimpleEnclosedRegion
TransformSurfaceOpaqueRegion(
80 const SimpleEnclosedRegion
& region
,
82 const gfx::Rect
& clip_rect_in_new_target
,
83 const gfx::Transform
& transform
) {
87 // Verify that rects within the |surface| will remain rects in its target
88 // surface after applying |transform|. If this is true, then apply |transform|
89 // to each rect within |region| in order to transform the entire Region.
91 // TODO(danakj): Find a rect interior to each transformed quad.
92 if (!transform
.Preserves2dAxisAlignment())
93 return SimpleEnclosedRegion();
95 SimpleEnclosedRegion transformed_region
;
96 for (size_t i
= 0; i
< region
.GetRegionComplexity(); ++i
) {
97 gfx::Rect transformed_rect
=
98 MathUtil::MapEnclosedRectWith2dAxisAlignedTransform(transform
,
101 transformed_rect
.Intersect(clip_rect_in_new_target
);
102 transformed_region
.Union(transformed_rect
);
104 return transformed_region
;
107 static inline bool LayerOpacityKnown(const Layer
* layer
) {
108 return !layer
->draw_opacity_is_animating();
110 static inline bool LayerOpacityKnown(const LayerImpl
* layer
) {
113 static inline bool LayerTransformsToTargetKnown(const Layer
* layer
) {
114 return !layer
->draw_transform_is_animating();
116 static inline bool LayerTransformsToTargetKnown(const LayerImpl
* layer
) {
120 static inline bool SurfaceOpacityKnown(const RenderSurface
* rs
) {
121 return !rs
->draw_opacity_is_animating();
123 static inline bool SurfaceOpacityKnown(const RenderSurfaceImpl
* rs
) {
126 static inline bool SurfaceTransformsToTargetKnown(const RenderSurface
* rs
) {
127 return !rs
->target_surface_transforms_are_animating();
129 static inline bool SurfaceTransformsToTargetKnown(const RenderSurfaceImpl
* rs
) {
132 static inline bool SurfaceTransformsToScreenKnown(const RenderSurface
* rs
) {
133 return !rs
->screen_space_transforms_are_animating();
135 static inline bool SurfaceTransformsToScreenKnown(const RenderSurfaceImpl
* rs
) {
139 static inline bool LayerIsInUnsorted3dRenderingContext(const Layer
* layer
) {
140 return layer
->Is3dSorted();
142 static inline bool LayerIsInUnsorted3dRenderingContext(const LayerImpl
* layer
) {
146 template <typename LayerType
>
147 static inline bool LayerIsHidden(const LayerType
* layer
) {
148 return layer
->hide_layer_and_subtree() ||
149 (layer
->parent() && LayerIsHidden(layer
->parent()));
152 template <typename LayerType
>
153 void OcclusionTracker
<LayerType
>::EnterRenderTarget(
154 const LayerType
* new_target
) {
155 if (!stack_
.empty() && stack_
.back().target
== new_target
)
158 const LayerType
* old_target
= NULL
;
159 const typename
LayerType::RenderSurfaceType
* old_occlusion_immune_ancestor
=
161 if (!stack_
.empty()) {
162 old_target
= stack_
.back().target
;
163 old_occlusion_immune_ancestor
=
164 old_target
->render_surface()->nearest_occlusion_immune_ancestor();
166 const typename
LayerType::RenderSurfaceType
* new_occlusion_immune_ancestor
=
167 new_target
->render_surface()->nearest_occlusion_immune_ancestor();
169 stack_
.push_back(StackObject(new_target
));
171 // We copy the screen occlusion into the new RenderSurface subtree, but we
172 // never copy in the occlusion from inside the target, since we are looking
173 // at a new RenderSurface target.
175 // If entering an unoccluded subtree, do not carry forward the outside
176 // occlusion calculated so far.
177 bool entering_unoccluded_subtree
=
178 new_occlusion_immune_ancestor
&&
179 new_occlusion_immune_ancestor
!= old_occlusion_immune_ancestor
;
181 bool have_transform_from_screen_to_new_target
= false;
182 gfx::Transform
inverse_new_target_screen_space_transform(
183 // Note carefully, not used if screen space transform is uninvertible.
184 gfx::Transform::kSkipInitialization
);
185 if (SurfaceTransformsToScreenKnown(new_target
->render_surface())) {
186 have_transform_from_screen_to_new_target
=
187 new_target
->render_surface()->screen_space_transform().GetInverse(
188 &inverse_new_target_screen_space_transform
);
191 bool entering_root_target
= new_target
->parent() == NULL
;
193 bool copy_outside_occlusion_forward
=
195 !entering_unoccluded_subtree
&&
196 have_transform_from_screen_to_new_target
&&
197 !entering_root_target
;
198 if (!copy_outside_occlusion_forward
)
201 int last_index
= stack_
.size() - 1;
202 gfx::Transform
old_target_to_new_target_transform(
203 inverse_new_target_screen_space_transform
,
204 old_target
->render_surface()->screen_space_transform());
205 stack_
[last_index
].occlusion_from_outside_target
=
206 TransformSurfaceOpaqueRegion
<typename
LayerType::RenderSurfaceType
>(
207 stack_
[last_index
- 1].occlusion_from_outside_target
,
210 old_target_to_new_target_transform
);
211 stack_
[last_index
].occlusion_from_outside_target
.Union(
212 TransformSurfaceOpaqueRegion
<typename
LayerType::RenderSurfaceType
>(
213 stack_
[last_index
- 1].occlusion_from_inside_target
,
216 old_target_to_new_target_transform
));
219 template <typename LayerType
>
220 void OcclusionTracker
<LayerType
>::FinishedRenderTarget(
221 const LayerType
* finished_target
) {
222 // Make sure we know about the target surface.
223 EnterRenderTarget(finished_target
);
225 typename
LayerType::RenderSurfaceType
* surface
=
226 finished_target
->render_surface();
228 // Readbacks always happen on render targets so we only need to check
229 // for readbacks here.
230 bool target_is_only_for_copy_request
=
231 finished_target
->HasCopyRequest() && LayerIsHidden(finished_target
);
233 // If the occlusion within the surface can not be applied to things outside of
234 // the surface's subtree, then clear the occlusion here so it won't be used.
235 if (finished_target
->mask_layer() || !SurfaceOpacityKnown(surface
) ||
236 surface
->draw_opacity() < 1 ||
237 !finished_target
->uses_default_blend_mode() ||
238 target_is_only_for_copy_request
||
239 finished_target
->filters().HasFilterThatAffectsOpacity()) {
240 stack_
.back().occlusion_from_outside_target
.Clear();
241 stack_
.back().occlusion_from_inside_target
.Clear();
242 } else if (!SurfaceTransformsToTargetKnown(surface
)) {
243 stack_
.back().occlusion_from_inside_target
.Clear();
244 stack_
.back().occlusion_from_outside_target
.Clear();
248 template <typename LayerType
>
249 static void ReduceOcclusionBelowSurface(
250 LayerType
* contributing_layer
,
251 const gfx::Rect
& surface_rect
,
252 const gfx::Transform
& surface_transform
,
253 LayerType
* render_target
,
254 SimpleEnclosedRegion
* occlusion_from_inside_target
) {
255 if (surface_rect
.IsEmpty())
258 gfx::Rect affected_area_in_target
=
259 MathUtil::MapEnclosingClippedRect(surface_transform
, surface_rect
);
260 if (contributing_layer
->render_surface()->is_clipped()) {
261 affected_area_in_target
.Intersect(
262 contributing_layer
->render_surface()->clip_rect());
264 if (affected_area_in_target
.IsEmpty())
267 int outset_top
, outset_right
, outset_bottom
, outset_left
;
268 contributing_layer
->background_filters().GetOutsets(
269 &outset_top
, &outset_right
, &outset_bottom
, &outset_left
);
271 // The filter can move pixels from outside of the clip, so allow affected_area
272 // to expand outside the clip.
273 affected_area_in_target
.Inset(
274 -outset_left
, -outset_top
, -outset_right
, -outset_bottom
);
275 SimpleEnclosedRegion affected_occlusion
= *occlusion_from_inside_target
;
276 affected_occlusion
.Intersect(affected_area_in_target
);
278 occlusion_from_inside_target
->Subtract(affected_area_in_target
);
279 for (size_t i
= 0; i
< affected_occlusion
.GetRegionComplexity(); ++i
) {
280 gfx::Rect occlusion_rect
= affected_occlusion
.GetRect(i
);
282 // Shrink the rect by expanding the non-opaque pixels outside the rect.
284 // The left outset of the filters moves pixels on the right side of
285 // the occlusion_rect into it, shrinking its right edge.
287 occlusion_rect
.x() == affected_area_in_target
.x() ? 0 : outset_right
;
289 occlusion_rect
.y() == affected_area_in_target
.y() ? 0 : outset_bottom
;
291 occlusion_rect
.right() == affected_area_in_target
.right() ?
294 occlusion_rect
.bottom() == affected_area_in_target
.bottom() ?
297 occlusion_rect
.Inset(shrink_left
, shrink_top
, shrink_right
, shrink_bottom
);
299 occlusion_from_inside_target
->Union(occlusion_rect
);
303 template <typename LayerType
>
304 void OcclusionTracker
<LayerType
>::LeaveToRenderTarget(
305 const LayerType
* new_target
) {
306 int last_index
= stack_
.size() - 1;
307 bool surface_will_be_at_top_after_pop
=
308 stack_
.size() > 1 && stack_
[last_index
- 1].target
== new_target
;
310 // We merge the screen occlusion from the current RenderSurfaceImpl subtree
311 // out to its parent target RenderSurfaceImpl. The target occlusion can be
312 // merged out as well but needs to be transformed to the new target.
314 const LayerType
* old_target
= stack_
[last_index
].target
;
315 const typename
LayerType::RenderSurfaceType
* old_surface
=
316 old_target
->render_surface();
318 SimpleEnclosedRegion old_occlusion_from_inside_target_in_new_target
=
319 TransformSurfaceOpaqueRegion
<typename
LayerType::RenderSurfaceType
>(
320 stack_
[last_index
].occlusion_from_inside_target
,
321 old_surface
->is_clipped(),
322 old_surface
->clip_rect(),
323 old_surface
->draw_transform());
324 if (old_target
->has_replica() && !old_target
->replica_has_mask()) {
325 old_occlusion_from_inside_target_in_new_target
.Union(
326 TransformSurfaceOpaqueRegion
<typename
LayerType::RenderSurfaceType
>(
327 stack_
[last_index
].occlusion_from_inside_target
,
328 old_surface
->is_clipped(),
329 old_surface
->clip_rect(),
330 old_surface
->replica_draw_transform()));
333 SimpleEnclosedRegion old_occlusion_from_outside_target_in_new_target
=
334 TransformSurfaceOpaqueRegion
<typename
LayerType::RenderSurfaceType
>(
335 stack_
[last_index
].occlusion_from_outside_target
,
338 old_surface
->draw_transform());
340 gfx::Rect unoccluded_surface_rect
;
341 gfx::Rect unoccluded_replica_rect
;
342 if (old_target
->background_filters().HasFilterThatMovesPixels()) {
343 unoccluded_surface_rect
= UnoccludedContributingSurfaceContentRect(
344 old_surface
->content_rect(), old_surface
->draw_transform());
345 if (old_target
->has_replica()) {
346 unoccluded_replica_rect
= UnoccludedContributingSurfaceContentRect(
347 old_surface
->content_rect(),
348 old_surface
->replica_draw_transform());
352 if (surface_will_be_at_top_after_pop
) {
353 // Merge the top of the stack down.
354 stack_
[last_index
- 1].occlusion_from_inside_target
.Union(
355 old_occlusion_from_inside_target_in_new_target
);
356 // TODO(danakj): Strictly this should subtract the inside target occlusion
358 if (new_target
->parent()) {
359 stack_
[last_index
- 1].occlusion_from_outside_target
.Union(
360 old_occlusion_from_outside_target_in_new_target
);
364 // Replace the top of the stack with the new pushed surface.
365 stack_
.back().target
= new_target
;
366 stack_
.back().occlusion_from_inside_target
=
367 old_occlusion_from_inside_target_in_new_target
;
368 if (new_target
->parent()) {
369 stack_
.back().occlusion_from_outside_target
=
370 old_occlusion_from_outside_target_in_new_target
;
372 stack_
.back().occlusion_from_outside_target
.Clear();
376 if (!old_target
->background_filters().HasFilterThatMovesPixels())
379 ReduceOcclusionBelowSurface(old_target
,
380 unoccluded_surface_rect
,
381 old_surface
->draw_transform(),
383 &stack_
.back().occlusion_from_inside_target
);
384 ReduceOcclusionBelowSurface(old_target
,
385 unoccluded_surface_rect
,
386 old_surface
->draw_transform(),
388 &stack_
.back().occlusion_from_outside_target
);
390 if (!old_target
->has_replica())
392 ReduceOcclusionBelowSurface(old_target
,
393 unoccluded_replica_rect
,
394 old_surface
->replica_draw_transform(),
396 &stack_
.back().occlusion_from_inside_target
);
397 ReduceOcclusionBelowSurface(old_target
,
398 unoccluded_replica_rect
,
399 old_surface
->replica_draw_transform(),
401 &stack_
.back().occlusion_from_outside_target
);
404 template <typename LayerType
>
405 void OcclusionTracker
<LayerType
>::MarkOccludedBehindLayer(
406 const LayerType
* layer
) {
407 DCHECK(!stack_
.empty());
408 DCHECK_EQ(layer
->render_target(), stack_
.back().target
);
410 if (!LayerOpacityKnown(layer
) || layer
->draw_opacity() < 1)
413 if (!layer
->uses_default_blend_mode())
416 if (LayerIsInUnsorted3dRenderingContext(layer
))
419 if (!LayerTransformsToTargetKnown(layer
))
422 SimpleEnclosedRegion opaque_contents
= layer
->VisibleContentOpaqueRegion();
423 if (opaque_contents
.IsEmpty())
426 DCHECK(layer
->visible_content_rect().Contains(opaque_contents
.bounds()));
428 // TODO(danakj): Find a rect interior to each transformed quad.
429 if (!layer
->draw_transform().Preserves2dAxisAlignment())
432 gfx::Rect clip_rect_in_target
= ScreenSpaceClipRectInTargetSurface(
433 layer
->render_target()->render_surface(), screen_space_clip_rect_
);
434 if (layer
->is_clipped()) {
435 clip_rect_in_target
.Intersect(layer
->clip_rect());
437 clip_rect_in_target
.Intersect(
438 layer
->render_target()->render_surface()->content_rect());
441 for (size_t i
= 0; i
< opaque_contents
.GetRegionComplexity(); ++i
) {
442 gfx::Rect transformed_rect
=
443 MathUtil::MapEnclosedRectWith2dAxisAlignedTransform(
444 layer
->draw_transform(), opaque_contents
.GetRect(i
));
445 transformed_rect
.Intersect(clip_rect_in_target
);
446 if (transformed_rect
.width() < minimum_tracking_size_
.width() &&
447 transformed_rect
.height() < minimum_tracking_size_
.height())
449 stack_
.back().occlusion_from_inside_target
.Union(transformed_rect
);
451 if (!occluding_screen_space_rects_
)
454 // Save the occluding area in screen space for debug visualization.
456 gfx::QuadF screen_space_quad
= MathUtil::MapQuad(
457 layer
->render_target()->render_surface()->screen_space_transform(),
458 gfx::QuadF(transformed_rect
), &clipped
);
459 // TODO(danakj): Store the quad in the debug info instead of the bounding
461 gfx::Rect screen_space_rect
=
462 gfx::ToEnclosedRect(screen_space_quad
.BoundingBox());
463 occluding_screen_space_rects_
->push_back(screen_space_rect
);
466 if (!non_occluding_screen_space_rects_
)
469 Region
non_opaque_contents(gfx::Rect(layer
->content_bounds()));
470 non_opaque_contents
.Subtract(opaque_contents
);
472 for (Region::Iterator
non_opaque_content_rects(non_opaque_contents
);
473 non_opaque_content_rects
.has_rect();
474 non_opaque_content_rects
.next()) {
475 gfx::Rect transformed_rect
=
476 MathUtil::MapEnclosedRectWith2dAxisAlignedTransform(
477 layer
->draw_transform(), non_opaque_content_rects
.rect());
478 transformed_rect
.Intersect(clip_rect_in_target
);
479 if (transformed_rect
.IsEmpty())
483 gfx::QuadF screen_space_quad
= MathUtil::MapQuad(
484 layer
->render_target()->render_surface()->screen_space_transform(),
485 gfx::QuadF(transformed_rect
),
487 // TODO(danakj): Store the quad in the debug info instead of the bounding
489 gfx::Rect screen_space_rect
=
490 gfx::ToEnclosedRect(screen_space_quad
.BoundingBox());
491 non_occluding_screen_space_rects_
->push_back(screen_space_rect
);
495 template <typename LayerType
>
496 gfx::Rect OcclusionTracker
<LayerType
>::UnoccludedContributingSurfaceContentRect(
497 const gfx::Rect
& content_rect
,
498 const gfx::Transform
& draw_transform
) const {
499 if (content_rect
.IsEmpty())
502 // A contributing surface doesn't get occluded by things inside its own
503 // surface, so only things outside the surface can occlude it. That occlusion
504 // is found just below the top of the stack (if it exists).
505 bool has_occlusion
= stack_
.size() > 1;
509 const StackObject
& second_last
= stack_
[stack_
.size() - 2];
510 if (second_last
.occlusion_from_inside_target
.IsEmpty() &&
511 second_last
.occlusion_from_outside_target
.IsEmpty())
514 gfx::Transform
inverse_draw_transform(gfx::Transform::kSkipInitialization
);
515 bool ok
= draw_transform
.GetInverse(&inverse_draw_transform
);
518 // Take the ToEnclosingRect at each step, as we want to contain any unoccluded
519 // partial pixels in the resulting Rect.
520 gfx::Rect unoccluded_rect_in_target_surface
=
521 MathUtil::MapEnclosingClippedRect(draw_transform
, content_rect
);
522 DCHECK_LE(second_last
.occlusion_from_inside_target
.GetRegionComplexity(), 1u);
523 DCHECK_LE(second_last
.occlusion_from_outside_target
.GetRegionComplexity(),
525 // These subtract operations are more lossy than if we did both operations at
527 unoccluded_rect_in_target_surface
.Subtract(
528 second_last
.occlusion_from_inside_target
.bounds());
529 unoccluded_rect_in_target_surface
.Subtract(
530 second_last
.occlusion_from_outside_target
.bounds());
532 if (unoccluded_rect_in_target_surface
.IsEmpty())
535 gfx::Rect unoccluded_rect
= MathUtil::ProjectEnclosingClippedRect(
536 inverse_draw_transform
, unoccluded_rect_in_target_surface
);
537 unoccluded_rect
.Intersect(content_rect
);
539 return unoccluded_rect
;
542 template <typename LayerType
>
543 Region OcclusionTracker
<LayerType
>::ComputeVisibleRegionInScreen() const {
544 DCHECK(!stack_
.back().target
->parent());
545 const SimpleEnclosedRegion
& occluded
=
546 stack_
.back().occlusion_from_inside_target
;
547 Region
visible_region(screen_space_clip_rect_
);
548 for (size_t i
= 0; i
< occluded
.GetRegionComplexity(); ++i
)
549 visible_region
.Subtract(occluded
.GetRect(i
));
550 return visible_region
;
553 // Instantiate (and export) templates here for the linker.
554 template class OcclusionTracker
<Layer
>;
555 template class OcclusionTracker
<LayerImpl
>;