1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407
|
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/trees/occlusion_tracker.h"
#include <stddef.h>
#include <algorithm>
#include "cc/base/math_util.h"
#include "cc/base/region.h"
#include "cc/layers/layer.h"
#include "cc/layers/layer_impl.h"
#include "cc/layers/render_surface_impl.h"
#include "cc/trees/layer_tree_impl.h"
#include "ui/gfx/geometry/quad_f.h"
#include "ui/gfx/geometry/rect_conversions.h"
namespace cc {
OcclusionTracker::OcclusionTracker(const gfx::Rect& screen_space_clip_rect)
: screen_space_clip_rect_(screen_space_clip_rect) {}
OcclusionTracker::~OcclusionTracker() = default;
Occlusion OcclusionTracker::GetCurrentOcclusionForLayer(
const gfx::Transform& draw_transform) const {
DCHECK(!stack_.empty());
const StackObject& back = stack_.back();
return Occlusion(draw_transform, back.occlusion_from_outside_target,
back.occlusion_from_inside_target);
}
Occlusion OcclusionTracker::GetCurrentOcclusionForContributingSurface(
const gfx::Transform& draw_transform) const {
DCHECK(!stack_.empty());
if (stack_.size() < 2 || stack_.back().ignores_parent_occlusion)
return Occlusion();
// A contributing surface doesn't get occluded by things inside its own
// surface, so only things outside the surface can occlude it. That occlusion
// is found just below the top of the stack (if it exists).
const StackObject& second_last = stack_[stack_.size() - 2];
return Occlusion(draw_transform, second_last.occlusion_from_outside_target,
second_last.occlusion_from_inside_target);
}
const RenderSurfaceImpl*
OcclusionTracker::OcclusionSurfaceForContributingSurface() const {
if (stack_.size() < 2 || stack_.back().ignores_parent_occlusion)
return nullptr;
// A contributing surface doesn't get occluded by things inside its own
// surface, so only things outside the surface can occlude it. That occlusion
// is found just below the top of the stack (if it exists).
return stack_[stack_.size() - 2].target;
}
void OcclusionTracker::EnterLayer(
const EffectTreeLayerListIterator::Position& iterator) {
RenderSurfaceImpl* render_target = iterator.target_render_surface;
if (iterator.state == EffectTreeLayerListIterator::State::kLayer) {
EnterRenderTarget(render_target);
} else if (iterator.state ==
EffectTreeLayerListIterator::State::kTargetSurface) {
FinishedRenderTarget(render_target);
}
}
void OcclusionTracker::LeaveLayer(
const EffectTreeLayerListIterator::Position& iterator) {
RenderSurfaceImpl* render_target = iterator.target_render_surface;
if (iterator.state == EffectTreeLayerListIterator::State::kLayer) {
MarkOccludedBehindLayer(iterator.current_layer);
}
// TODO(danakj): This should be done when entering the contributing surface,
// but in a way that the surface's own occlusion won't occlude itself.
else if (iterator.state ==
EffectTreeLayerListIterator::State::kContributingSurface) {
LeaveToRenderTarget(render_target);
}
}
static gfx::Rect ScreenSpaceClipRectInTargetSurface(
const RenderSurfaceImpl* target_surface,
const gfx::Rect& screen_space_clip_rect) {
gfx::Transform inverse_screen_space_transform;
if (!target_surface->screen_space_transform().GetInverse(
&inverse_screen_space_transform))
return target_surface->content_rect();
return MathUtil::ProjectEnclosingClippedRect(inverse_screen_space_transform,
screen_space_clip_rect);
}
static SimpleEnclosedRegion TransformSurfaceOpaqueRegion(
const SimpleEnclosedRegion& region,
bool have_clip_rect,
const gfx::Rect& clip_rect_in_new_target,
const gfx::Transform& transform) {
if (region.IsEmpty())
return region;
// Verify that rects within the |surface| will remain rects in its target
// surface after applying |transform|. If this is true, then apply |transform|
// to each rect within |region| in order to transform the entire Region.
// TODO(danakj): Find a rect interior to each transformed quad.
if (!transform.NonDegeneratePreserves2dAxisAlignment())
return SimpleEnclosedRegion();
SimpleEnclosedRegion transformed_region;
for (size_t i = 0; i < region.GetRegionComplexity(); ++i) {
gfx::Rect transformed_rect =
MathUtil::MapEnclosedRectWith2dAxisAlignedTransform(transform,
region.GetRect(i));
if (have_clip_rect)
transformed_rect.Intersect(clip_rect_in_new_target);
transformed_region.Union(transformed_rect);
}
return transformed_region;
}
void OcclusionTracker::EnterRenderTarget(
const RenderSurfaceImpl* new_target_surface) {
DCHECK(new_target_surface);
if (!stack_.empty() && stack_.back().target == new_target_surface)
return;
const RenderSurfaceImpl* old_target_surface = nullptr;
const RenderSurfaceImpl* old_occlusion_immune_ancestor = nullptr;
if (!stack_.empty()) {
old_target_surface = stack_.back().target;
old_occlusion_immune_ancestor =
old_target_surface->nearest_occlusion_immune_ancestor();
}
const RenderSurfaceImpl* new_occlusion_immune_ancestor =
new_target_surface->nearest_occlusion_immune_ancestor();
stack_.emplace_back(new_target_surface);
// We copy the screen occlusion into the new RenderSurfaceImpl subtree, but we
// never copy in the occlusion from inside the target, since we are looking
// at a new RenderSurfaceImpl target.
// If entering an unoccluded subtree, do not carry forward the outside
// occlusion calculated so far.
bool entering_unoccluded_subtree =
new_occlusion_immune_ancestor &&
new_occlusion_immune_ancestor != old_occlusion_immune_ancestor;
gfx::Transform inverse_new_target_screen_space_transform;
bool have_transform_from_screen_to_new_target =
new_target_surface->screen_space_transform().GetInverse(
&inverse_new_target_screen_space_transform);
bool entering_root_target =
new_target_surface->render_target() == new_target_surface;
bool copy_outside_occlusion_forward =
stack_.size() > 1 && !entering_unoccluded_subtree &&
have_transform_from_screen_to_new_target && !entering_root_target;
if (!copy_outside_occlusion_forward) {
stack_.back().ignores_parent_occlusion = true;
return;
}
size_t last_index = stack_.size() - 1;
gfx::Transform old_target_to_new_target_transform =
inverse_new_target_screen_space_transform *
old_target_surface->screen_space_transform();
stack_[last_index].occlusion_from_outside_target =
TransformSurfaceOpaqueRegion(
stack_[last_index - 1].occlusion_from_outside_target, false,
gfx::Rect(), old_target_to_new_target_transform);
stack_[last_index].occlusion_from_outside_target.Union(
TransformSurfaceOpaqueRegion(
stack_[last_index - 1].occlusion_from_inside_target, false,
gfx::Rect(), old_target_to_new_target_transform));
}
// A blend mode is occluding if a fully opaque source can fully occlude the
// destination and the result is also fully opaque.
static bool IsOccludingBlendMode(SkBlendMode blend_mode) {
return blend_mode == SkBlendMode::kSrc || blend_mode == SkBlendMode::kSrcOver;
}
void OcclusionTracker::FinishedRenderTarget(
const RenderSurfaceImpl* finished_target_surface) {
// Make sure we know about the target surface.
EnterRenderTarget(finished_target_surface);
bool is_hidden =
finished_target_surface->OwningEffectNode()->screen_space_opacity == 0.f;
// Readbacks always happen on render targets so we only need to check
// for readbacks here.
bool target_is_only_for_copy_request_or_force_render_surface =
is_hidden && finished_target_surface->CopyOfOutputRequired();
// If the occlusion within the surface can not be applied to things outside of
// the surface's subtree, then clear the occlusion here so it won't be used.
if (finished_target_surface->HasMaskingContributingSurface() ||
finished_target_surface->draw_opacity() < 1 ||
!IsOccludingBlendMode(finished_target_surface->BlendMode()) ||
target_is_only_for_copy_request_or_force_render_surface ||
finished_target_surface->Filters().HasFilterThatAffectsOpacity() ||
finished_target_surface->OwningEffectNode()
->view_transition_element_resource_id.IsValid()) {
stack_.back().occlusion_from_outside_target.Clear();
stack_.back().occlusion_from_inside_target.Clear();
}
}
static void ReduceOcclusionBelowSurface(
const RenderSurfaceImpl* contributing_surface,
const gfx::Rect& surface_rect,
const gfx::Transform& surface_transform,
SimpleEnclosedRegion* occlusion_from_inside_target) {
if (surface_rect.IsEmpty())
return;
gfx::Rect target_rect =
MathUtil::MapEnclosingClippedRect(surface_transform, surface_rect);
if (contributing_surface->is_clipped()) {
target_rect.Intersect(contributing_surface->clip_rect());
}
if (target_rect.IsEmpty())
return;
gfx::Rect affected_area_in_target =
contributing_surface->BackdropFilters().MapRectReverse(target_rect,
SkMatrix::I());
// Unite target_rect because we only care about positive outsets.
affected_area_in_target.Union(target_rect);
SimpleEnclosedRegion affected_occlusion = *occlusion_from_inside_target;
affected_occlusion.Intersect(affected_area_in_target);
occlusion_from_inside_target->Subtract(affected_area_in_target);
for (size_t i = 0; i < affected_occlusion.GetRegionComplexity(); ++i) {
gfx::Rect occlusion_rect = affected_occlusion.GetRect(i);
// Shrink the rect by expanding the non-opaque pixels outside the rect.
// The left outset of the filters moves pixels on the right side of
// the occlusion_rect into it, shrinking its right edge.
int shrink_left =
occlusion_rect.x() == affected_area_in_target.x()
? 0
: affected_area_in_target.right() - target_rect.right();
int shrink_top =
occlusion_rect.y() == affected_area_in_target.y()
? 0
: affected_area_in_target.bottom() - target_rect.bottom();
int shrink_right = occlusion_rect.right() == affected_area_in_target.right()
? 0
: target_rect.x() - affected_area_in_target.x();
int shrink_bottom =
occlusion_rect.bottom() == affected_area_in_target.bottom()
? 0
: target_rect.y() - affected_area_in_target.y();
occlusion_rect.Inset(gfx::Insets::TLBR(shrink_top, shrink_left,
shrink_bottom, shrink_right));
occlusion_from_inside_target->Union(occlusion_rect);
}
}
void OcclusionTracker::LeaveToRenderTarget(
const RenderSurfaceImpl* new_target_surface) {
DCHECK(!stack_.empty());
size_t last_index = stack_.size() - 1;
DCHECK(new_target_surface);
bool surface_will_be_at_top_after_pop =
stack_.size() > 1 && stack_[last_index - 1].target == new_target_surface;
// We merge the screen occlusion from the current RenderSurfaceImpl subtree
// out to its parent target RenderSurfaceImpl. The target occlusion can be
// merged out as well but needs to be transformed to the new target.
const RenderSurfaceImpl* old_surface = stack_[last_index].target;
SimpleEnclosedRegion old_occlusion_from_inside_target_in_new_target =
TransformSurfaceOpaqueRegion(
stack_[last_index].occlusion_from_inside_target,
old_surface->is_clipped(), old_surface->clip_rect(),
old_surface->draw_transform());
SimpleEnclosedRegion old_occlusion_from_outside_target_in_new_target =
TransformSurfaceOpaqueRegion(
stack_[last_index].occlusion_from_outside_target, false, gfx::Rect(),
old_surface->draw_transform());
gfx::Rect unoccluded_surface_rect;
if (old_surface->BackdropFilters().HasFilterThatMovesPixels()) {
Occlusion surface_occlusion = GetCurrentOcclusionForContributingSurface(
old_surface->draw_transform());
unoccluded_surface_rect =
surface_occlusion.GetUnoccludedContentRect(old_surface->content_rect());
}
bool is_root = new_target_surface->render_target() == new_target_surface;
if (surface_will_be_at_top_after_pop) {
// Merge the top of the stack down.
stack_[last_index - 1].occlusion_from_inside_target.Union(
old_occlusion_from_inside_target_in_new_target);
// TODO(danakj): Strictly this should subtract the inside target occlusion
// before union.
if (!is_root) {
stack_[last_index - 1].occlusion_from_outside_target.Union(
old_occlusion_from_outside_target_in_new_target);
}
stack_.pop_back();
} else {
// Replace the top of the stack with the new pushed surface.
stack_.back().target = new_target_surface;
stack_.back().occlusion_from_inside_target =
old_occlusion_from_inside_target_in_new_target;
if (!is_root) {
stack_.back().occlusion_from_outside_target =
old_occlusion_from_outside_target_in_new_target;
} else {
stack_.back().occlusion_from_outside_target.Clear();
}
}
if (!old_surface->BackdropFilters().HasFilterThatMovesPixels())
return;
ReduceOcclusionBelowSurface(old_surface, unoccluded_surface_rect,
old_surface->draw_transform(),
&stack_.back().occlusion_from_inside_target);
ReduceOcclusionBelowSurface(old_surface, unoccluded_surface_rect,
old_surface->draw_transform(),
&stack_.back().occlusion_from_outside_target);
}
void OcclusionTracker::MarkOccludedBehindLayer(const LayerImpl* layer) {
DCHECK(!stack_.empty());
DCHECK_EQ(layer->render_target(), stack_.back().target);
if (layer->draw_opacity() < 1)
return;
if (layer->Is3dSorted())
return;
if (!layer->draw_properties().mask_filter_info.IsEmpty())
return;
SimpleEnclosedRegion opaque_layer_region = layer->VisibleOpaqueRegion();
if (opaque_layer_region.IsEmpty())
return;
// If the blend mode is not occluding and the effect doesn't have a render
// surface, then the layer should not occlude. An example of this would
// otherwise be wrong is that this layer is a non-render-surface mask layer
// with kDstIn blend mode.
const auto* effect_node =
layer->layer_tree_impl()->property_trees()->effect_tree().Node(
layer->effect_tree_index());
if (!effect_node->HasRenderSurface() &&
!IsOccludingBlendMode(effect_node->blend_mode))
return;
DCHECK(layer->visible_layer_rect().Contains(opaque_layer_region.bounds()));
gfx::Transform draw_transform = layer->DrawTransform();
// TODO(danakj): Find a rect interior to each transformed quad.
if (!draw_transform.NonDegeneratePreserves2dAxisAlignment())
return;
gfx::Rect clip_rect_in_target = ScreenSpaceClipRectInTargetSurface(
layer->render_target(), screen_space_clip_rect_);
if (layer->is_clipped()) {
clip_rect_in_target.Intersect(layer->clip_rect());
} else {
clip_rect_in_target.Intersect(layer->render_target()->content_rect());
}
for (size_t i = 0; i < opaque_layer_region.GetRegionComplexity(); ++i) {
gfx::Rect transformed_rect =
MathUtil::MapEnclosedRectWith2dAxisAlignedTransform(
draw_transform, opaque_layer_region.GetRect(i));
transformed_rect.Intersect(clip_rect_in_target);
if (transformed_rect.width() < minimum_tracking_size_.width() &&
transformed_rect.height() < minimum_tracking_size_.height())
continue;
stack_.back().occlusion_from_inside_target.Union(transformed_rect);
}
}
Region OcclusionTracker::ComputeVisibleRegionInScreen(
const LayerTreeImpl* layer_tree) const {
DCHECK(layer_tree->RootRenderSurface() == stack_.back().target);
const SimpleEnclosedRegion& occluded =
stack_.back().occlusion_from_inside_target;
Region visible_region(screen_space_clip_rect_);
for (size_t i = 0; i < occluded.GetRegionComplexity(); ++i)
visible_region.Subtract(occluded.GetRect(i));
return visible_region;
}
} // namespace cc
|