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_impl.h"
14 #include "ui/gfx/geometry/quad_f.h"
15 #include "ui/gfx/geometry/rect_conversions.h"
19 OcclusionTracker::OcclusionTracker(const gfx::Rect
& screen_space_clip_rect
)
20 : screen_space_clip_rect_(screen_space_clip_rect
) {
23 OcclusionTracker::~OcclusionTracker() {
26 Occlusion
OcclusionTracker::GetCurrentOcclusionForLayer(
27 const gfx::Transform
& draw_transform
) const {
28 DCHECK(!stack_
.empty());
29 const StackObject
& back
= stack_
.back();
30 return Occlusion(draw_transform
,
31 back
.occlusion_from_outside_target
,
32 back
.occlusion_from_inside_target
);
35 Occlusion
OcclusionTracker::GetCurrentOcclusionForContributingSurface(
36 const gfx::Transform
& draw_transform
) const {
37 DCHECK(!stack_
.empty());
38 if (stack_
.size() < 2)
40 // A contributing surface doesn't get occluded by things inside its own
41 // surface, so only things outside the surface can occlude it. That occlusion
42 // is found just below the top of the stack (if it exists).
43 const StackObject
& second_last
= stack_
[stack_
.size() - 2];
44 return Occlusion(draw_transform
, second_last
.occlusion_from_outside_target
,
45 second_last
.occlusion_from_inside_target
);
48 void OcclusionTracker::EnterLayer(const LayerIteratorPosition
& layer_iterator
) {
49 LayerImpl
* render_target
= layer_iterator
.target_render_surface_layer
;
51 if (layer_iterator
.represents_itself
)
52 EnterRenderTarget(render_target
);
53 else if (layer_iterator
.represents_target_render_surface
)
54 FinishedRenderTarget(render_target
);
57 void OcclusionTracker::LeaveLayer(const LayerIteratorPosition
& layer_iterator
) {
58 LayerImpl
* render_target
= layer_iterator
.target_render_surface_layer
;
60 if (layer_iterator
.represents_itself
)
61 MarkOccludedBehindLayer(layer_iterator
.current_layer
);
62 // TODO(danakj): This should be done when entering the contributing surface,
63 // but in a way that the surface's own occlusion won't occlude itself.
64 else if (layer_iterator
.represents_contributing_render_surface
)
65 LeaveToRenderTarget(render_target
);
68 static gfx::Rect
ScreenSpaceClipRectInTargetSurface(
69 const RenderSurfaceImpl
* target_surface
,
70 const gfx::Rect
& screen_space_clip_rect
) {
71 gfx::Transform
inverse_screen_space_transform(
72 gfx::Transform::kSkipInitialization
);
73 if (!target_surface
->screen_space_transform().GetInverse(
74 &inverse_screen_space_transform
))
75 return target_surface
->content_rect();
77 return MathUtil::ProjectEnclosingClippedRect(inverse_screen_space_transform
,
78 screen_space_clip_rect
);
81 static SimpleEnclosedRegion
TransformSurfaceOpaqueRegion(
82 const SimpleEnclosedRegion
& region
,
84 const gfx::Rect
& clip_rect_in_new_target
,
85 const gfx::Transform
& transform
) {
89 // Verify that rects within the |surface| will remain rects in its target
90 // surface after applying |transform|. If this is true, then apply |transform|
91 // to each rect within |region| in order to transform the entire Region.
93 // TODO(danakj): Find a rect interior to each transformed quad.
94 if (!transform
.Preserves2dAxisAlignment())
95 return SimpleEnclosedRegion();
97 SimpleEnclosedRegion transformed_region
;
98 for (size_t i
= 0; i
< region
.GetRegionComplexity(); ++i
) {
99 gfx::Rect transformed_rect
=
100 MathUtil::MapEnclosedRectWith2dAxisAlignedTransform(transform
,
103 transformed_rect
.Intersect(clip_rect_in_new_target
);
104 transformed_region
.Union(transformed_rect
);
106 return transformed_region
;
109 void OcclusionTracker::EnterRenderTarget(const LayerImpl
* new_target
) {
110 if (!stack_
.empty() && stack_
.back().target
== new_target
)
113 const LayerImpl
* old_target
= NULL
;
114 const RenderSurfaceImpl
* old_occlusion_immune_ancestor
= NULL
;
115 if (!stack_
.empty()) {
116 old_target
= stack_
.back().target
;
117 old_occlusion_immune_ancestor
=
118 old_target
->render_surface()->nearest_occlusion_immune_ancestor();
120 const RenderSurfaceImpl
* new_occlusion_immune_ancestor
=
121 new_target
->render_surface()->nearest_occlusion_immune_ancestor();
123 stack_
.push_back(StackObject(new_target
));
125 // We copy the screen occlusion into the new RenderSurfaceImpl subtree, but we
126 // never copy in the occlusion from inside the target, since we are looking
127 // at a new RenderSurfaceImpl target.
129 // If entering an unoccluded subtree, do not carry forward the outside
130 // occlusion calculated so far.
131 bool entering_unoccluded_subtree
=
132 new_occlusion_immune_ancestor
&&
133 new_occlusion_immune_ancestor
!= old_occlusion_immune_ancestor
;
135 gfx::Transform
inverse_new_target_screen_space_transform(
136 // Note carefully, not used if screen space transform is uninvertible.
137 gfx::Transform::kSkipInitialization
);
138 bool have_transform_from_screen_to_new_target
=
139 new_target
->render_surface()->screen_space_transform().GetInverse(
140 &inverse_new_target_screen_space_transform
);
142 bool entering_root_target
= new_target
->parent() == NULL
;
144 bool copy_outside_occlusion_forward
=
146 !entering_unoccluded_subtree
&&
147 have_transform_from_screen_to_new_target
&&
148 !entering_root_target
;
149 if (!copy_outside_occlusion_forward
)
152 size_t last_index
= stack_
.size() - 1;
153 gfx::Transform
old_target_to_new_target_transform(
154 inverse_new_target_screen_space_transform
,
155 old_target
->render_surface()->screen_space_transform());
156 stack_
[last_index
].occlusion_from_outside_target
=
157 TransformSurfaceOpaqueRegion(
158 stack_
[last_index
- 1].occlusion_from_outside_target
, false,
159 gfx::Rect(), old_target_to_new_target_transform
);
160 stack_
[last_index
].occlusion_from_outside_target
.Union(
161 TransformSurfaceOpaqueRegion(
162 stack_
[last_index
- 1].occlusion_from_inside_target
, false,
163 gfx::Rect(), old_target_to_new_target_transform
));
166 static bool LayerIsHidden(const LayerImpl
* layer
) {
167 return layer
->hide_layer_and_subtree() ||
168 (layer
->parent() && LayerIsHidden(layer
->parent()));
171 void OcclusionTracker::FinishedRenderTarget(const LayerImpl
* finished_target
) {
172 // Make sure we know about the target surface.
173 EnterRenderTarget(finished_target
);
175 RenderSurfaceImpl
* surface
= finished_target
->render_surface();
177 // Readbacks always happen on render targets so we only need to check
178 // for readbacks here.
179 bool target_is_only_for_copy_request
=
180 finished_target
->HasCopyRequest() && LayerIsHidden(finished_target
);
182 // If the occlusion within the surface can not be applied to things outside of
183 // the surface's subtree, then clear the occlusion here so it won't be used.
184 if (finished_target
->mask_layer() || surface
->draw_opacity() < 1 ||
185 !finished_target
->uses_default_blend_mode() ||
186 target_is_only_for_copy_request
||
187 finished_target
->filters().HasFilterThatAffectsOpacity()) {
188 stack_
.back().occlusion_from_outside_target
.Clear();
189 stack_
.back().occlusion_from_inside_target
.Clear();
193 static void ReduceOcclusionBelowSurface(
194 const LayerImpl
* contributing_layer
,
195 const gfx::Rect
& surface_rect
,
196 const gfx::Transform
& surface_transform
,
197 const LayerImpl
* render_target
,
198 SimpleEnclosedRegion
* occlusion_from_inside_target
) {
199 if (surface_rect
.IsEmpty())
202 gfx::Rect affected_area_in_target
=
203 MathUtil::MapEnclosingClippedRect(surface_transform
, surface_rect
);
204 if (contributing_layer
->render_surface()->is_clipped()) {
205 affected_area_in_target
.Intersect(
206 contributing_layer
->render_surface()->clip_rect());
208 if (affected_area_in_target
.IsEmpty())
211 int outset_top
, outset_right
, outset_bottom
, outset_left
;
212 contributing_layer
->background_filters().GetOutsets(
213 &outset_top
, &outset_right
, &outset_bottom
, &outset_left
);
215 // The filter can move pixels from outside of the clip, so allow affected_area
216 // to expand outside the clip.
217 affected_area_in_target
.Inset(
218 -outset_left
, -outset_top
, -outset_right
, -outset_bottom
);
219 SimpleEnclosedRegion affected_occlusion
= *occlusion_from_inside_target
;
220 affected_occlusion
.Intersect(affected_area_in_target
);
222 occlusion_from_inside_target
->Subtract(affected_area_in_target
);
223 for (size_t i
= 0; i
< affected_occlusion
.GetRegionComplexity(); ++i
) {
224 gfx::Rect occlusion_rect
= affected_occlusion
.GetRect(i
);
226 // Shrink the rect by expanding the non-opaque pixels outside the rect.
228 // The left outset of the filters moves pixels on the right side of
229 // the occlusion_rect into it, shrinking its right edge.
231 occlusion_rect
.x() == affected_area_in_target
.x() ? 0 : outset_right
;
233 occlusion_rect
.y() == affected_area_in_target
.y() ? 0 : outset_bottom
;
235 occlusion_rect
.right() == affected_area_in_target
.right() ?
238 occlusion_rect
.bottom() == affected_area_in_target
.bottom() ?
241 occlusion_rect
.Inset(shrink_left
, shrink_top
, shrink_right
, shrink_bottom
);
243 occlusion_from_inside_target
->Union(occlusion_rect
);
247 void OcclusionTracker::LeaveToRenderTarget(const LayerImpl
* new_target
) {
248 DCHECK(!stack_
.empty());
249 size_t last_index
= stack_
.size() - 1;
250 bool surface_will_be_at_top_after_pop
=
251 stack_
.size() > 1 && stack_
[last_index
- 1].target
== new_target
;
253 // We merge the screen occlusion from the current RenderSurfaceImpl subtree
254 // out to its parent target RenderSurfaceImpl. The target occlusion can be
255 // merged out as well but needs to be transformed to the new target.
257 const LayerImpl
* old_target
= stack_
[last_index
].target
;
258 const RenderSurfaceImpl
* old_surface
= old_target
->render_surface();
260 SimpleEnclosedRegion old_occlusion_from_inside_target_in_new_target
=
261 TransformSurfaceOpaqueRegion(
262 stack_
[last_index
].occlusion_from_inside_target
,
263 old_surface
->is_clipped(), old_surface
->clip_rect(),
264 old_surface
->draw_transform());
265 if (old_target
->has_replica() && !old_target
->replica_has_mask()) {
266 old_occlusion_from_inside_target_in_new_target
.Union(
267 TransformSurfaceOpaqueRegion(
268 stack_
[last_index
].occlusion_from_inside_target
,
269 old_surface
->is_clipped(), old_surface
->clip_rect(),
270 old_surface
->replica_draw_transform()));
273 SimpleEnclosedRegion old_occlusion_from_outside_target_in_new_target
=
274 TransformSurfaceOpaqueRegion(
275 stack_
[last_index
].occlusion_from_outside_target
, false, gfx::Rect(),
276 old_surface
->draw_transform());
278 gfx::Rect unoccluded_surface_rect
;
279 gfx::Rect unoccluded_replica_rect
;
280 if (old_target
->background_filters().HasFilterThatMovesPixels()) {
281 Occlusion surface_occlusion
= GetCurrentOcclusionForContributingSurface(
282 old_surface
->draw_transform());
283 unoccluded_surface_rect
=
284 surface_occlusion
.GetUnoccludedContentRect(old_surface
->content_rect());
285 if (old_target
->has_replica()) {
286 Occlusion replica_occlusion
= GetCurrentOcclusionForContributingSurface(
287 old_surface
->replica_draw_transform());
288 unoccluded_replica_rect
= replica_occlusion
.GetUnoccludedContentRect(
289 old_surface
->content_rect());
293 if (surface_will_be_at_top_after_pop
) {
294 // Merge the top of the stack down.
295 stack_
[last_index
- 1].occlusion_from_inside_target
.Union(
296 old_occlusion_from_inside_target_in_new_target
);
297 // TODO(danakj): Strictly this should subtract the inside target occlusion
299 if (new_target
->parent()) {
300 stack_
[last_index
- 1].occlusion_from_outside_target
.Union(
301 old_occlusion_from_outside_target_in_new_target
);
305 // Replace the top of the stack with the new pushed surface.
306 stack_
.back().target
= new_target
;
307 stack_
.back().occlusion_from_inside_target
=
308 old_occlusion_from_inside_target_in_new_target
;
309 if (new_target
->parent()) {
310 stack_
.back().occlusion_from_outside_target
=
311 old_occlusion_from_outside_target_in_new_target
;
313 stack_
.back().occlusion_from_outside_target
.Clear();
317 if (!old_target
->background_filters().HasFilterThatMovesPixels())
320 ReduceOcclusionBelowSurface(old_target
,
321 unoccluded_surface_rect
,
322 old_surface
->draw_transform(),
324 &stack_
.back().occlusion_from_inside_target
);
325 ReduceOcclusionBelowSurface(old_target
,
326 unoccluded_surface_rect
,
327 old_surface
->draw_transform(),
329 &stack_
.back().occlusion_from_outside_target
);
331 if (!old_target
->has_replica())
333 ReduceOcclusionBelowSurface(old_target
,
334 unoccluded_replica_rect
,
335 old_surface
->replica_draw_transform(),
337 &stack_
.back().occlusion_from_inside_target
);
338 ReduceOcclusionBelowSurface(old_target
,
339 unoccluded_replica_rect
,
340 old_surface
->replica_draw_transform(),
342 &stack_
.back().occlusion_from_outside_target
);
345 void OcclusionTracker::MarkOccludedBehindLayer(const LayerImpl
* layer
) {
346 DCHECK(!stack_
.empty());
347 DCHECK_EQ(layer
->render_target(), stack_
.back().target
);
349 if (layer
->draw_opacity() < 1)
352 if (!layer
->uses_default_blend_mode())
355 if (layer
->Is3dSorted())
358 SimpleEnclosedRegion opaque_layer_region
= layer
->VisibleOpaqueRegion();
359 if (opaque_layer_region
.IsEmpty())
362 DCHECK(layer
->visible_layer_rect().Contains(opaque_layer_region
.bounds()));
364 // TODO(danakj): Find a rect interior to each transformed quad.
365 if (!layer
->draw_transform().Preserves2dAxisAlignment())
368 gfx::Rect clip_rect_in_target
= ScreenSpaceClipRectInTargetSurface(
369 layer
->render_target()->render_surface(), screen_space_clip_rect_
);
370 if (layer
->is_clipped()) {
371 clip_rect_in_target
.Intersect(layer
->clip_rect());
373 clip_rect_in_target
.Intersect(
374 layer
->render_target()->render_surface()->content_rect());
377 for (size_t i
= 0; i
< opaque_layer_region
.GetRegionComplexity(); ++i
) {
378 gfx::Rect transformed_rect
=
379 MathUtil::MapEnclosedRectWith2dAxisAlignedTransform(
380 layer
->draw_transform(), opaque_layer_region
.GetRect(i
));
381 transformed_rect
.Intersect(clip_rect_in_target
);
382 if (transformed_rect
.width() < minimum_tracking_size_
.width() &&
383 transformed_rect
.height() < minimum_tracking_size_
.height())
385 stack_
.back().occlusion_from_inside_target
.Union(transformed_rect
);
389 Region
OcclusionTracker::ComputeVisibleRegionInScreen() const {
390 DCHECK(!stack_
.back().target
->parent());
391 const SimpleEnclosedRegion
& occluded
=
392 stack_
.back().occlusion_from_inside_target
;
393 Region
visible_region(screen_space_clip_rect_
);
394 for (size_t i
= 0; i
< occluded
.GetRegionComplexity(); ++i
)
395 visible_region
.Subtract(occluded
.GetRect(i
));
396 return visible_region
;