diff options
-rw-r--r-- | cc/layers/delegated_renderer_layer_impl.cc | 1 | ||||
-rw-r--r-- | cc/layers/draw_properties.h | 5 | ||||
-rw-r--r-- | cc/layers/layer.h | 3 | ||||
-rw-r--r-- | cc/layers/layer_impl.cc | 5 | ||||
-rw-r--r-- | cc/layers/layer_impl.h | 3 | ||||
-rw-r--r-- | cc/trees/layer_tree_host_common.cc | 435 | ||||
-rw-r--r-- | cc/trees/layer_tree_host_common_unittest.cc | 556 | ||||
-rw-r--r-- | cc/trees/layer_tree_host_impl.cc | 8 |
8 files changed, 929 insertions, 87 deletions
diff --git a/cc/layers/delegated_renderer_layer_impl.cc b/cc/layers/delegated_renderer_layer_impl.cc index 426dcbb..17a0c1c 100644 --- a/cc/layers/delegated_renderer_layer_impl.cc +++ b/cc/layers/delegated_renderer_layer_impl.cc @@ -421,6 +421,7 @@ void DelegatedRendererLayerImpl::AppendRenderPassQuads( if (render_target() == this) { DCHECK(!is_clipped()); DCHECK(render_surface()); + DCHECK_EQ(0, num_unclipped_descendants()); output_shared_quad_state->clip_rect = MathUtil::MapClippedRect( delegated_frame_to_target_transform, output_shared_quad_state->clip_rect); diff --git a/cc/layers/draw_properties.h b/cc/layers/draw_properties.h index bb80dce..181d900 100644 --- a/cc/layers/draw_properties.h +++ b/cc/layers/draw_properties.h @@ -27,6 +27,7 @@ struct CC_EXPORT DrawProperties { contents_scale_x(1.f), contents_scale_y(1.f), num_descendants_that_draw_content(0), + num_unclipped_descendants(0), descendants_can_clip_selves(false), can_draw_directly_to_backbuffer(false), layer_or_descendant_has_copy_request(false) {} @@ -88,6 +89,10 @@ struct CC_EXPORT DrawProperties { // Does not include this layer itself, only its children and descendants. int num_descendants_that_draw_content; + // Number of descendants with a clip parent that is our ancestor. NB - this + // does not include our clip children because they are clipped by us. + int num_unclipped_descendants; + // If true, every descendant in the sub-tree can clip itself without the // need to use hardware sissoring or a new render target. bool descendants_can_clip_selves; diff --git a/cc/layers/layer.h b/cc/layers/layer.h index ff3ff20..b659ab2 100644 --- a/cc/layers/layer.h +++ b/cc/layers/layer.h @@ -236,6 +236,9 @@ class CC_EXPORT Layer : public base::RefCounted<Layer>, RenderSurface* render_surface() const { return draw_properties_.render_surface.get(); } + int num_unclipped_descendants() const { + return draw_properties_.num_unclipped_descendants; + } void SetScrollOffset(gfx::Vector2d scroll_offset); gfx::Vector2d scroll_offset() const { return scroll_offset_; } diff --git a/cc/layers/layer_impl.cc b/cc/layers/layer_impl.cc index 3a92195..8dcf1ff 100644 --- a/cc/layers/layer_impl.cc +++ b/cc/layers/layer_impl.cc @@ -140,6 +140,9 @@ void LayerImpl::SetScrollParent(LayerImpl* parent) { if (scroll_parent_ == parent) return; + // Having both a scroll parent and a scroll offset delegate is unsupported. + DCHECK(!scroll_offset_delegate_); + if (scroll_parent_) scroll_parent_->RemoveScrollChild(this); @@ -988,6 +991,8 @@ void LayerImpl::UpdateScrollbarPositions() { void LayerImpl::SetScrollOffsetDelegate( LayerScrollOffsetDelegate* scroll_offset_delegate) { + // Having both a scroll parent and a scroll offset delegate is unsupported. + DCHECK(!scroll_parent_); if (!scroll_offset_delegate && scroll_offset_delegate_) { scroll_delta_ = scroll_offset_delegate_->GetTotalScrollOffset() - scroll_offset_; diff --git a/cc/layers/layer_impl.h b/cc/layers/layer_impl.h index f06bfe4..dc7508d 100644 --- a/cc/layers/layer_impl.h +++ b/cc/layers/layer_impl.h @@ -317,6 +317,9 @@ class CC_EXPORT LayerImpl : LayerAnimationValueObserver { RenderSurfaceImpl* render_surface() const { return draw_properties_.render_surface.get(); } + int num_unclipped_descendants() const { + return draw_properties_.num_unclipped_descendants; + } // The client should be responsible for setting bounds, content bounds and // contents scale to appropriate values. LayerImpl doesn't calculate any of diff --git a/cc/trees/layer_tree_host_common.cc b/cc/trees/layer_tree_host_common.cc index dba7b50..70060b2 100644 --- a/cc/trees/layer_tree_host_common.cc +++ b/cc/trees/layer_tree_host_common.cc @@ -40,6 +40,29 @@ static void SortLayers(LayerImplList::iterator first, layer_sorter->Sort(first, end); } +template <typename LayerType> +static gfx::Vector2dF GetEffectiveScrollDelta(LayerType* layer) { + gfx::Vector2dF scroll_delta = layer->ScrollDelta(); + // The scroll parent's scroll delta is the amount we've scrolled on the + // compositor thread since the commit for this layer tree's source frame. + // we last reported to the main thread. I.e., it's the discrepancy between + // a scroll parent's scroll delta and offset, so we must add it here. + if (layer->scroll_parent()) + scroll_delta += layer->scroll_parent()->ScrollDelta(); + return scroll_delta; +} + +template <typename LayerType> +static gfx::Vector2dF GetEffectiveTotalScrollOffset(LayerType* layer) { + gfx::Vector2dF offset = layer->TotalScrollOffset(); + // The scroll parent's total scroll offset (scroll offset + scroll delta) + // can't be used because its scroll offset has already been applied to the + // scroll children's positions by the main thread layer positioning code. + if (layer->scroll_parent()) + offset += layer->scroll_parent()->ScrollDelta(); + return offset; +} + inline gfx::Rect CalculateVisibleRectWithCachedLayerRect( gfx::Rect target_surface_rect, gfx::Rect layer_bound_rect, @@ -90,6 +113,196 @@ gfx::Rect LayerTreeHostCommon::CalculateVisibleRect( target_surface_rect, layer_bound_rect, layer_in_surface_space, transform); } +template <typename LayerType> +static LayerType* NextTargetSurface(LayerType* layer) { + return layer->parent() ? layer->parent()->render_target() : 0; +} + +// Given two layers, this function finds their respective render targets and, +// computes a change of basis translation. It does this by accumulating the +// translation components of the draw transforms of each target between the +// ancestor and descendant. These transforms must be 2D translations, and this +// requirement is enforced at every step. +template <typename LayerType, typename RenderSurfaceType> +static gfx::Vector2dF ComputeChangeOfBasisTranslation( + const LayerType& ancestor_layer, + const LayerType& descendant_layer) { + DCHECK(descendant_layer.HasAncestor(&ancestor_layer)); + const LayerType* descendant_target = descendant_layer.render_target(); + DCHECK(descendant_target); + const LayerType* ancestor_target = ancestor_layer.render_target(); + DCHECK(ancestor_target); + + gfx::Vector2dF translation; + for (const LayerType* target = descendant_target; target != ancestor_target; + target = NextTargetSurface(target)) + translation += target->render_surface()->draw_transform().To2dTranslation(); + + return translation; +} + +enum TranslateRectDirection { + TranslateRectDirectionToAncestor, + TranslateRectDirectionToDescendant +}; + +template <typename LayerType, typename RenderSurfaceType> +static gfx::Rect TranslateRectToTargetSpace(const LayerType& ancestor_layer, + const LayerType& descendant_layer, + gfx::Rect rect, + TranslateRectDirection direction) { + gfx::Vector2dF translation = + ComputeChangeOfBasisTranslation<LayerType, RenderSurfaceType>( + ancestor_layer, descendant_layer); + if (direction == TranslateRectDirectionToDescendant) + translation.Scale(-1.f); + return gfx::ToEnclosingRect( + gfx::RectF(rect.origin() + translation, rect.size())); +} + +// Attempts to update the clip rects for the given layer. If the layer has a +// clip_parent, it may not inherit its immediate ancestor's clip. +template <typename LayerType, typename RenderSurfaceType> +static void UpdateClipRectsForClipChild( + const LayerType* layer, + gfx::Rect* clip_rect_in_parent_target_space, + bool* subtree_should_be_clipped) { + // If the layer has no clip_parent, or the ancestor is the same as its actual + // parent, then we don't need special clip rects. Bail now and leave the out + // parameters untouched. + const LayerType* clip_parent = layer->clip_parent(); + if (!clip_parent || clip_parent == layer->parent()) + return; + + // The root layer is never a clip child. + DCHECK(layer->parent()); + + // Grab the cached values. + *clip_rect_in_parent_target_space = clip_parent->clip_rect(); + *subtree_should_be_clipped = clip_parent->is_clipped(); + + // We may have to project the clip rect into our parent's target space. Note, + // it must be our parent's target space, not ours. For one, we haven't + // computed our transforms, so we couldn't put it in our space yet even if we + // wanted to. But more importantly, this matches the expectations of + // CalculateDrawPropertiesInternal. If we, say, create a render surface, these + // clip rects will want to be in its target space, not ours. + *clip_rect_in_parent_target_space = + TranslateRectToTargetSpace<LayerType, RenderSurfaceType>( + *clip_parent, + *layer->parent(), + *clip_rect_in_parent_target_space, + TranslateRectDirectionToDescendant); +} + +// We collect an accumulated drawable content rect per render surface. +// Typically, a layer will contribute to only one surface, the surface +// associated with its render target. Clip children, however, may affect +// several surfaces since there may be several surfaces between the clip child +// and its parent. +// +// NB: we accumulate the layer's *clipped* drawable content rect. +template <typename LayerType> +struct AccumulatedSurfaceState { + explicit AccumulatedSurfaceState(LayerType* render_target) + : render_target(render_target) {} + + // The accumulated drawable content rect for the surface associated with the + // given |render_target|. + gfx::Rect drawable_content_rect; + + // The target owning the surface. (We hang onto the target rather than the + // surface so that we can DCHECK that the surface's draw transform is simply + // a translation when |render_target| reports that it has no unclipped + // descendants). + LayerType* render_target; +}; + +template <typename LayerType, typename RenderSurfaceType> +void UpdateAccumulatedSurfaceState( + LayerType* layer, + gfx::Rect drawable_content_rect, + std::vector<AccumulatedSurfaceState<LayerType> >* + accumulated_surface_state) { + if (IsRootLayer(layer)) + return; + + // We will apply our drawable content rect to the accumulated rects for all + // surfaces between us and |render_target| (inclusive). This is either our + // clip parent's target if we are a clip child, or else simply our parent's + // target. We use our parent's target because we're either the owner of a + // render surface and we'll want to add our rect to our *surface's* target, or + // we're not and our target is the same as our parent's. In both cases, the + // parent's target gives us what we want. + LayerType* render_target = layer->clip_parent() + ? layer->clip_parent()->render_target() + : layer->parent()->render_target(); + + // If the layer owns a surface, then the content rect is in the wrong space. + // Instead, we will use the surface's DrawableContentRect which is in target + // space as required. + gfx::Rect target_rect = drawable_content_rect; + if (layer->render_surface()) { + target_rect = + gfx::ToEnclosedRect(layer->render_surface()->DrawableContentRect()); + } + + if (render_target->is_clipped()) { + gfx::Rect clip_rect = render_target->clip_rect(); + // If the layer has a clip parent, the clip rect may be in the wrong space, + // so we'll need to transform it before it is applied. + if (layer->clip_parent()) { + clip_rect = TranslateRectToTargetSpace<LayerType, RenderSurfaceType>( + *layer->clip_parent(), + *layer, + clip_rect, + TranslateRectDirectionToDescendant); + } + target_rect.Intersect(clip_rect); + } + + // We must have at least one entry in the vector for the root. + DCHECK_LT(0ul, accumulated_surface_state->size()); + + typedef typename std::vector<AccumulatedSurfaceState<LayerType> > + AccumulatedSurfaceStateVector; + typedef typename AccumulatedSurfaceStateVector::reverse_iterator + AccumulatedSurfaceStateIterator; + AccumulatedSurfaceStateIterator current_state = + accumulated_surface_state->rbegin(); + + // Add this rect to the accumulated content rect for all surfaces until we + // reach the target surface. + bool found_render_target = false; + for (; current_state != accumulated_surface_state->rend(); ++current_state) { + current_state->drawable_content_rect.Union(target_rect); + + // If we've reached |render_target| our work is done and we can bail. + if (current_state->render_target == render_target) { + found_render_target = true; + break; + } + + // Transform rect from the current target's space to the next. + LayerType* current_target = current_state->render_target; + DCHECK(current_target->render_surface()); + const gfx::Transform& current_draw_transform = + current_target->render_surface()->draw_transform(); + + // If we have unclipped descendants, the draw transform is a translation. + DCHECK(current_target->num_unclipped_descendants() == 0 || + current_draw_transform.IsIdentityOrTranslation()); + + target_rect = gfx::ToEnclosingRect( + MathUtil::MapClippedRect(current_draw_transform, target_rect)); + } + + // It is an error to not reach |render_target|. If this happens, it means that + // either the clip parent is not an ancestor of the clip child or the surface + // state vector is empty, both of which should be impossible. + DCHECK(found_render_target); +} + template <typename LayerType> static inline bool IsRootLayer(LayerType* layer) { return !layer->parent(); } @@ -416,10 +629,6 @@ static bool SubtreeShouldRenderToSeparateSurface( return false; } -static LayerImpl* NextTargetSurface(LayerImpl* layer) { - return layer->parent() ? layer->parent()->render_target() : 0; -} - // This function returns a translation matrix that can be applied on a vector // that's in the layer's target surface coordinate, while the position offset is // specified in some ancestor layer's coordinate. @@ -550,9 +759,10 @@ gfx::Transform ComputeScrollCompensationForThisLayer( // gfx::Transform scroll_compensation_for_this_layer = parent_matrix; // Step 3 + gfx::Vector2dF scroll_delta = GetEffectiveScrollDelta(scrolling_layer); scroll_compensation_for_this_layer.Translate( - scrolling_layer->ScrollDelta().x(), - scrolling_layer->ScrollDelta().y()); // Step 2 + scroll_delta.x(), + scroll_delta.y()); // Step 2 gfx::Transform inverse_parent_matrix(gfx::Transform::kSkipInitialization); if (!parent_matrix.GetInverse(&inverse_parent_matrix)) { @@ -601,8 +811,9 @@ gfx::Transform ComputeScrollCompensationMatrixForChildren( // Avoid the overheads (including stack allocation and matrix // initialization/copy) if we know that the scroll compensation doesn't need // to be reset or adjusted. + gfx::Vector2dF scroll_delta = GetEffectiveScrollDelta(layer); if (!layer->IsContainerForFixedPositionLayers() && - layer->ScrollDelta().IsZero() && !layer->render_surface()) + scroll_delta.IsZero() && !layer->render_surface()) return current_scroll_compensation_matrix; // Start as identity matrix. @@ -616,7 +827,7 @@ gfx::Transform ComputeScrollCompensationMatrixForChildren( // If the current layer has a non-zero scroll_delta, then we should compute // its local scroll compensation and accumulate it to the // next_scroll_compensation_matrix. - if (!layer->ScrollDelta().IsZero()) { + if (!scroll_delta.IsZero()) { gfx::Transform scroll_compensation_for_this_layer = ComputeScrollCompensationForThisLayer( layer, parent_matrix); @@ -785,13 +996,17 @@ static inline void RemoveSurfaceForEarlyExit( struct PreCalculateMetaInformationRecursiveData { bool layer_or_descendant_has_copy_request; + int num_unclipped_descendants; PreCalculateMetaInformationRecursiveData() - : layer_or_descendant_has_copy_request(false) {} + : layer_or_descendant_has_copy_request(false), + num_unclipped_descendants(0) {} void Merge(const PreCalculateMetaInformationRecursiveData& data) { layer_or_descendant_has_copy_request |= data.layer_or_descendant_has_copy_request; + num_unclipped_descendants += + data.num_unclipped_descendants; } }; @@ -814,6 +1029,9 @@ static void PreCalculateMetaInformation( descendants_can_clip_selves = false; } + if (layer->clip_parent()) + recursive_data->num_unclipped_descendants++; + for (size_t i = 0; i < layer->children().size(); ++i) { LayerType* child_layer = LayerTreeHostCommon::get_child_as_raw_ptr(layer->children(), i); @@ -839,11 +1057,19 @@ static void PreCalculateMetaInformation( recursive_data->Merge(data_for_child); } + if (layer->clip_children()) { + int num_clip_children = layer->clip_children()->size(); + DCHECK_GE(recursive_data->num_unclipped_descendants, num_clip_children); + recursive_data->num_unclipped_descendants -= num_clip_children; + } + if (layer->HasCopyRequest()) recursive_data->layer_or_descendant_has_copy_request = true; layer->draw_properties().num_descendants_that_draw_content = num_descendants_that_draw_content; + layer->draw_properties().num_unclipped_descendants = + recursive_data->num_unclipped_descendants; layer->draw_properties().descendants_can_clip_selves = descendants_can_clip_selves; layer->draw_properties().layer_or_descendant_has_copy_request = @@ -917,7 +1143,8 @@ static void CalculateDrawPropertiesInternal( const DataForRecursion<LayerType, RenderSurfaceType>& data_from_ancestor, LayerListType* render_surface_layer_list, LayerListType* layer_list, - gfx::Rect* drawable_content_rect_of_subtree) { + std::vector<AccumulatedSurfaceState<LayerType> >* + accumulated_surface_state) { // This function computes the new matrix transformations recursively for this // layer and all its descendants. It also computes the appropriate render // surfaces. @@ -1045,10 +1272,6 @@ static void CalculateDrawPropertiesInternal( DCHECK(globals.page_scale_application_layer || (globals.page_scale_factor == 1.f)); - // If we early-exit anywhere in this function, the drawable_content_rect of - // this subtree should be considered empty. - *drawable_content_rect_of_subtree = gfx::Rect(); - DataForRecursion<LayerType, RenderSurfaceType> data_for_children; RenderSurfaceType* nearest_ancestor_surface_that_moves_pixels = data_from_ancestor.nearest_ancestor_surface_that_moves_pixels; @@ -1067,8 +1290,25 @@ static void CalculateDrawPropertiesInternal( layer_is_visible = true; // The root layer cannot skip CalcDrawProperties. - if (!IsRootLayer(layer) && SubtreeShouldBeSkipped(layer, layer_is_visible)) + if (!IsRootLayer(layer) && SubtreeShouldBeSkipped(layer, layer_is_visible)) { + if (layer->render_surface()) + layer->ClearRenderSurface(); return; + } + + // We need to circumvent the normal recursive flow of information for clip + // children (they don't inherit their direct ancestor's clip information). + // This is unfortunate, and would be unnecessary if we were to formally + // separate the clipping hierarchy from the layer hierarchy. + bool ancestor_clips_subtree = data_from_ancestor.ancestor_clips_subtree; + gfx::Rect ancestor_clip_rect_in_target_space = + data_from_ancestor.clip_rect_in_target_space; + + // Update our clipping state. If we have a clip parent we will need to pull + // from the clip state cache rather than using the clip state passed from our + // immediate ancestor. + UpdateClipRectsForClipChild<LayerType, RenderSurfaceType>( + layer, &ancestor_clip_rect_in_target_space, &ancestor_clips_subtree); // As this function proceeds, these are the properties for the current // layer that actually get computed. To avoid unnecessary copies @@ -1108,7 +1348,8 @@ static void CalculateDrawPropertiesInternal( gfx::Size bounds = layer->bounds(); gfx::PointF anchor_point = layer->anchor_point(); - gfx::PointF position = layer->position() - layer->TotalScrollOffset(); + gfx::Vector2dF scroll_offset = GetEffectiveTotalScrollOffset(layer); + gfx::PointF position = layer->position() - scroll_offset; gfx::Transform combined_transform = data_from_ancestor.parent_matrix; if (!layer->transform().IsIdentity()) { @@ -1285,13 +1526,6 @@ static void CalculateDrawPropertiesInternal( data_for_children.full_hierarchy_matrix.PreconcatTransform( render_surface->draw_transform()); - // The new render_surface here will correctly clip the entire subtree. So, - // we do not need to continue propagating the clipping state further down - // the tree. This way, we can avoid transforming clip rects from ancestor - // target surface space to current target surface space that could cause - // more w < 0 headaches. - layer_or_ancestor_clips_descendants = false; - if (layer->mask_layer()) { DrawProperties<LayerType, RenderSurfaceType>& mask_layer_draw_properties = layer->mask_layer()->draw_properties(); @@ -1314,14 +1548,15 @@ static void CalculateDrawPropertiesInternal( if (layer->filters().HasFilterThatMovesPixels() || layer->filter()) nearest_ancestor_surface_that_moves_pixels = render_surface; - // The render surface clip rect is expressed in the space where this surface - // draws, i.e. the same space as - // data_from_ancestor.clip_rect_in_target_space. - render_surface->SetIsClipped(data_from_ancestor.ancestor_clips_subtree); - if (data_from_ancestor.ancestor_clips_subtree) { - render_surface->SetClipRect( - data_from_ancestor.clip_rect_in_target_space); + render_surface->SetNearestAncestorThatMovesPixels( + nearest_ancestor_surface_that_moves_pixels); + layer_or_ancestor_clips_descendants = false; + bool subtree_is_clipped_by_surface_bounds = false; + if (ancestor_clips_subtree) { + // It may be the layer or the surface doing the clipping of the subtree, + // but in either case, we'll be clipping to the projected clip rect of our + // ancestor. gfx::Transform inverse_surface_draw_transform( gfx::Transform::kSkipInitialization); if (!render_surface->draw_transform().GetInverse( @@ -1329,18 +1564,48 @@ static void CalculateDrawPropertiesInternal( // TODO(shawnsingh): Either we need to handle uninvertible transforms // here, or DCHECK that the transform is invertible. } - clip_rect_of_target_surface_in_target_space = - gfx::ToEnclosingRect(MathUtil::ProjectClippedRect( - inverse_surface_draw_transform, render_surface->clip_rect())); - } else { + + gfx::Rect projected_surface_rect = gfx::ToEnclosingRect( + MathUtil::ProjectClippedRect(inverse_surface_draw_transform, + ancestor_clip_rect_in_target_space)); + + if (layer_draw_properties.num_unclipped_descendants > 0) { + // If we have unclipped descendants, we cannot count on the render + // surface's bounds clipping our subtree: the unclipped descendants + // could cause us to expand our bounds. In this case, we must rely on + // layer clipping for correctess. NB: since we can only encounter + // translations between a clip child and its clip parent, clipping is + // guaranteed to be exact in this case. + layer_or_ancestor_clips_descendants = true; + clip_rect_in_target_space = projected_surface_rect; + } else { + // The new render_surface here will correctly clip the entire subtree. + // So, we do not need to continue propagating the clipping state further + // down the tree. This way, we can avoid transforming clip rects from + // ancestor target surface space to current target surface space that + // could cause more w < 0 headaches. The render surface clip rect is + // expressed in the space where this surface draws, i.e. the same space + // as clip_rect_from_ancestor_in_ancestor_target_space. + render_surface->SetClipRect(ancestor_clip_rect_in_target_space); + clip_rect_of_target_surface_in_target_space = projected_surface_rect; + subtree_is_clipped_by_surface_bounds = true; + } + } + + DCHECK(layer->render_surface()); + DCHECK(!layer->parent() || layer->parent()->render_target() == + accumulated_surface_state->back().render_target); + + accumulated_surface_state->push_back( + AccumulatedSurfaceState<LayerType>(layer)); + + render_surface->SetIsClipped(subtree_is_clipped_by_surface_bounds); + if (!subtree_is_clipped_by_surface_bounds) { render_surface->SetClipRect(gfx::Rect()); clip_rect_of_target_surface_in_target_space = data_from_ancestor.clip_rect_of_target_surface_in_target_space; } - render_surface->SetNearestAncestorThatMovesPixels( - nearest_ancestor_surface_that_moves_pixels); - // If the new render surface is drawn translucent or with a non-integral // translation then the subtree that gets drawn on this render surface // cannot use LCD text. @@ -1366,11 +1631,10 @@ static void CalculateDrawPropertiesInternal( // Layers without render_surfaces directly inherit the ancestor's clip // status. - layer_or_ancestor_clips_descendants = - data_from_ancestor.ancestor_clips_subtree; - if (data_from_ancestor.ancestor_clips_subtree) { + layer_or_ancestor_clips_descendants = ancestor_clips_subtree; + if (ancestor_clips_subtree) { clip_rect_in_target_space = - data_from_ancestor.clip_rect_in_target_space; + ancestor_clip_rect_in_target_space; } // The surface's cached clip rect value propagates regardless of what @@ -1406,16 +1670,30 @@ static void CalculateDrawPropertiesInternal( if (LayerClipsSubtree(layer)) { layer_or_ancestor_clips_descendants = true; - if (data_from_ancestor.ancestor_clips_subtree && !layer->render_surface()) { + if (ancestor_clips_subtree && !layer->render_surface()) { // A layer without render surface shares the same target as its ancestor. clip_rect_in_target_space = - data_from_ancestor.clip_rect_in_target_space; + ancestor_clip_rect_in_target_space; clip_rect_in_target_space.Intersect(rect_in_target_space); } else { clip_rect_in_target_space = rect_in_target_space; } } + // Tell the layer the rect that it's clipped by. In theory we could use a + // tighter clip rect here (drawable_content_rect), but that actually does not + // reduce how much would be drawn, and instead it would create unnecessary + // changes to scissor state affecting GPU performance. Our clip information + // is used in the recursion below, so we must set it beforehand. + layer_draw_properties.is_clipped = layer_or_ancestor_clips_descendants; + if (layer_or_ancestor_clips_descendants) { + layer_draw_properties.clip_rect = clip_rect_in_target_space; + } else { + // Initialize the clip rect to a safe value that will not clip the + // layer, just in case clipping is still accidentally used. + layer_draw_properties.clip_rect = rect_in_target_space; + } + LayerListType& descendants = (layer->render_surface() ? layer->render_surface()->layer_list() : *layer_list); @@ -1470,7 +1748,6 @@ static void CalculateDrawPropertiesInternal( data_for_children.subtree_is_visible_from_ancestor = layer_is_visible; } - gfx::Rect accumulated_drawable_content_rect_of_children; for (size_t i = 0; i < layer->children().size(); ++i) { LayerType* child = LayerTreeHostCommon::get_child_as_raw_ptr(layer->children(), i); @@ -1484,29 +1761,34 @@ static void CalculateDrawPropertiesInternal( data_for_children, render_surface_layer_list, &descendants, - &drawable_content_rect_of_child_subtree); - if (!drawable_content_rect_of_child_subtree.IsEmpty()) { - accumulated_drawable_content_rect_of_children.Union( - drawable_content_rect_of_child_subtree); - if (child->render_surface()) - descendants.push_back(child); + accumulated_surface_state); + if (child->render_surface() && + !child->render_surface()->content_rect().IsEmpty()) { + descendants.push_back(child); } } + // Compute the total drawable_content_rect for this subtree (the rect is in + // target surface space). + gfx::Rect local_drawable_content_rect_of_subtree = + accumulated_surface_state->back().drawable_content_rect; + if (layer->render_surface()) { + DCHECK(accumulated_surface_state->back().render_target == layer); + accumulated_surface_state->pop_back(); + } + if (layer->render_surface() && !IsRootLayer(layer) && layer->render_surface()->layer_list().empty()) { RemoveSurfaceForEarlyExit(layer, render_surface_layer_list); return; } - // Compute the total drawable_content_rect for this subtree (the rect is in - // target surface space). - gfx::Rect local_drawable_content_rect_of_subtree = - accumulated_drawable_content_rect_of_children; - if (layer->DrawsContent()) - local_drawable_content_rect_of_subtree.Union(rect_in_target_space); - if (layer_or_ancestor_clips_descendants) - local_drawable_content_rect_of_subtree.Intersect(clip_rect_in_target_space); + if (layer->DrawsContent()) { + gfx::Rect local_drawable_content_rect = rect_in_target_space; + if (layer_or_ancestor_clips_descendants) + local_drawable_content_rect.Intersect(clip_rect_in_target_space); + local_drawable_content_rect_of_subtree.Union(local_drawable_content_rect); + } // Compute the layer's drawable content rect (the rect is in target surface // space). @@ -1516,19 +1798,6 @@ static void CalculateDrawPropertiesInternal( Intersect(clip_rect_in_target_space); } - // Tell the layer the rect that is clipped by. In theory we could use a - // tighter clip rect here (drawable_content_rect), but that actually does not - // reduce how much would be drawn, and instead it would create unnecessary - // changes to scissor state affecting GPU performance. - layer_draw_properties.is_clipped = layer_or_ancestor_clips_descendants; - if (layer_or_ancestor_clips_descendants) { - layer_draw_properties.clip_rect = clip_rect_in_target_space; - } else { - // Initialize the clip rect to a safe value that will not clip the - // layer, just in case clipping is still accidentally used. - layer_draw_properties.clip_rect = rect_in_target_space; - } - // Compute the layer's visible content rect (the rect is in content space). layer_draw_properties.visible_content_rect = CalculateVisibleContentRect( layer, clip_rect_of_target_surface_in_target_space, rect_in_target_space); @@ -1539,7 +1808,7 @@ static void CalculateDrawPropertiesInternal( // The root layer's surface's content_rect is always the entire viewport. DCHECK(layer->render_surface()); layer->render_surface()->SetContentRect( - data_from_ancestor.clip_rect_in_target_space); + ancestor_clip_rect_in_target_space); } else if (layer->render_surface() && !IsRootLayer(layer)) { RenderSurfaceType* render_surface = layer->render_surface(); gfx::Rect clipped_content_rect = local_drawable_content_rect_of_subtree; @@ -1552,8 +1821,7 @@ static void CalculateDrawPropertiesInternal( // Note, it is correct to use data_from_ancestor.ancestor_clips_subtree // here, because we are looking at this layer's render_surface, not the // layer itself. - if (data_from_ancestor.ancestor_clips_subtree && - !clipped_content_rect.IsEmpty()) { + if (render_surface->is_clipped() && !clipped_content_rect.IsEmpty()) { gfx::Rect surface_clip_rect = LayerTreeHostCommon::CalculateVisibleRect( render_surface->clip_rect(), clipped_content_rect, @@ -1638,12 +1906,8 @@ static void CalculateDrawPropertiesInternal( globals.layer_sorter); } - if (layer->render_surface()) { - *drawable_content_rect_of_subtree = - gfx::ToEnclosingRect(layer->render_surface()->DrawableContentRect()); - } else { - *drawable_content_rect_of_subtree = local_drawable_content_rect_of_subtree; - } + UpdateAccumulatedSurfaceState<LayerType, RenderSurfaceType>( + layer, local_drawable_content_rect_of_subtree, accumulated_surface_state); if (layer->HasContributingDelegatedRenderPasses()) { layer->render_target()->render_surface()-> @@ -1656,7 +1920,6 @@ void LayerTreeHostCommon::CalculateDrawProperties( DCHECK(inputs->root_layer); DCHECK(IsRootLayer(inputs->root_layer)); DCHECK(inputs->render_surface_layer_list); - gfx::Rect total_drawable_content_rect; gfx::Transform identity_matrix; gfx::Transform scaled_device_transform = inputs->device_transform; scaled_device_transform.Scale(inputs->device_scale_factor, @@ -1691,14 +1954,14 @@ void LayerTreeHostCommon::CalculateDrawProperties( PreCalculateMetaInformationRecursiveData recursive_data; PreCalculateMetaInformation(inputs->root_layer, &recursive_data); - + std::vector<AccumulatedSurfaceState<Layer> > accumulated_surface_state; CalculateDrawPropertiesInternal<Layer, RenderSurfaceLayerList, RenderSurface>( inputs->root_layer, globals, data_for_recursion, inputs->render_surface_layer_list, &dummy_layer_list, - &total_drawable_content_rect); + &accumulated_surface_state); // The dummy layer list should not have been used. DCHECK_EQ(0u, dummy_layer_list.size()); @@ -1713,7 +1976,6 @@ void LayerTreeHostCommon::CalculateDrawProperties( DCHECK(IsRootLayer(inputs->root_layer)); DCHECK(inputs->render_surface_layer_list); - gfx::Rect total_drawable_content_rect; gfx::Transform identity_matrix; gfx::Transform scaled_device_transform = inputs->device_transform; scaled_device_transform.Scale(inputs->device_scale_factor, @@ -1749,14 +2011,15 @@ void LayerTreeHostCommon::CalculateDrawProperties( PreCalculateMetaInformationRecursiveData recursive_data; PreCalculateMetaInformation(inputs->root_layer, &recursive_data); - + std::vector<AccumulatedSurfaceState<LayerImpl> > + accumulated_surface_state; CalculateDrawPropertiesInternal<LayerImpl, LayerImplList, RenderSurfaceImpl>( inputs->root_layer, globals, data_for_recursion, inputs->render_surface_layer_list, &dummy_layer_list, - &total_drawable_content_rect); + &accumulated_surface_state); // The dummy layer list should not have been used. DCHECK_EQ(0u, dummy_layer_list.size()); diff --git a/cc/trees/layer_tree_host_common_unittest.cc b/cc/trees/layer_tree_host_common_unittest.cc index 49b80e8..c275bc85 100644 --- a/cc/trees/layer_tree_host_common_unittest.cc +++ b/cc/trees/layer_tree_host_common_unittest.cc @@ -8350,5 +8350,561 @@ TEST_F(LayerTreeHostCommonTest, VisibleContentRectInsideSurface) { surface_child->visible_content_rect().ToString()); } +TEST_F(LayerTreeHostCommonTest, TransformedClipParent) { + // Ensure that a transform between the layer and its render surface is not a + // problem. Constructs the following layer tree. + // + // root (a render surface) + // + render_surface + // + clip_parent (scaled) + // + intervening_clipping_layer + // + clip_child + // + // The render surface should be resized correctly and the clip child should + // inherit the right clip rect. + scoped_refptr<Layer> root = Layer::Create(); + scoped_refptr<Layer> render_surface = Layer::Create(); + scoped_refptr<Layer> clip_parent = Layer::Create(); + scoped_refptr<Layer> intervening = Layer::Create(); + scoped_refptr<LayerWithForcedDrawsContent> clip_child = + make_scoped_refptr(new LayerWithForcedDrawsContent); + + root->AddChild(render_surface); + render_surface->AddChild(clip_parent); + clip_parent->AddChild(intervening); + intervening->AddChild(clip_child); + + clip_child->SetClipParent(clip_parent.get()); + + intervening->SetMasksToBounds(true); + clip_parent->SetMasksToBounds(true); + + render_surface->SetForceRenderSurface(true); + + gfx::Transform scale_transform; + scale_transform.Scale(2, 2); + + gfx::Transform identity_transform; + + SetLayerPropertiesForTesting(root.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(50, 50), + false); + SetLayerPropertiesForTesting(render_surface.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(10, 10), + false); + SetLayerPropertiesForTesting(clip_parent.get(), + scale_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(1.f, 1.f), + gfx::Size(10, 10), + false); + SetLayerPropertiesForTesting(intervening.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(1.f, 1.f), + gfx::Size(5, 5), + false); + SetLayerPropertiesForTesting(clip_child.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(1.f, 1.f), + gfx::Size(10, 10), + false); + + scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create(); + host->SetRootLayer(root); + + ExecuteCalculateDrawProperties(root.get()); + + ASSERT_TRUE(root->render_surface()); + ASSERT_TRUE(render_surface->render_surface()); + + // Ensure that we've inherited our clip parent's clip and weren't affected + // by the intervening clip layer. + ASSERT_EQ(gfx::Rect(1, 1, 20, 20).ToString(), + clip_parent->clip_rect().ToString()); + ASSERT_EQ(clip_parent->clip_rect().ToString(), + clip_child->clip_rect().ToString()); + ASSERT_EQ(gfx::Rect(3, 3, 10, 10).ToString(), + intervening->clip_rect().ToString()); + + // Ensure that the render surface reports a content rect that has been grown + // to accomodate for the clip child. + ASSERT_EQ(gfx::Rect(5, 5, 16, 16).ToString(), + render_surface->render_surface()->content_rect().ToString()); + + // The above check implies the two below, but they nicely demonstrate that + // we've grown, despite the intervening layer's clip. + ASSERT_TRUE(clip_parent->clip_rect().Contains( + render_surface->render_surface()->content_rect())); + ASSERT_FALSE(intervening->clip_rect().Contains( + render_surface->render_surface()->content_rect())); +} + +TEST_F(LayerTreeHostCommonTest, ClipParentWithInterveningRenderSurface) { + // Ensure that intervening render surfaces are not a problem in the basic + // case. In the following tree, both render surfaces should be resized to + // accomodate for the clip child, despite an intervening clip. + // + // root (a render surface) + // + clip_parent (masks to bounds) + // + render_surface1 (sets opacity) + // + intervening (masks to bounds) + // + render_surface2 (also sets opacity) + // + clip_child + // + scoped_refptr<Layer> root = Layer::Create(); + scoped_refptr<Layer> clip_parent = Layer::Create(); + scoped_refptr<Layer> render_surface1 = Layer::Create(); + scoped_refptr<Layer> intervening = Layer::Create(); + scoped_refptr<Layer> render_surface2 = Layer::Create(); + scoped_refptr<LayerWithForcedDrawsContent> clip_child = + make_scoped_refptr(new LayerWithForcedDrawsContent); + + root->AddChild(clip_parent); + clip_parent->AddChild(render_surface1); + render_surface1->AddChild(intervening); + intervening->AddChild(render_surface2); + render_surface2->AddChild(clip_child); + + clip_child->SetClipParent(clip_parent.get()); + + intervening->SetMasksToBounds(true); + clip_parent->SetMasksToBounds(true); + + render_surface1->SetForceRenderSurface(true); + render_surface2->SetForceRenderSurface(true); + + gfx::Transform translation_transform; + translation_transform.Translate(2, 2); + + gfx::Transform identity_transform; + SetLayerPropertiesForTesting(root.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(50, 50), + false); + SetLayerPropertiesForTesting(clip_parent.get(), + translation_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(1.f, 1.f), + gfx::Size(40, 40), + false); + SetLayerPropertiesForTesting(render_surface1.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(10, 10), + false); + SetLayerPropertiesForTesting(intervening.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(1.f, 1.f), + gfx::Size(5, 5), + false); + SetLayerPropertiesForTesting(render_surface2.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(10, 10), + false); + SetLayerPropertiesForTesting(clip_child.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(-10.f, -10.f), + gfx::Size(60, 60), + false); + + scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create(); + host->SetRootLayer(root); + + ExecuteCalculateDrawProperties(root.get()); + + EXPECT_TRUE(root->render_surface()); + EXPECT_TRUE(render_surface1->render_surface()); + EXPECT_TRUE(render_surface2->render_surface()); + + // Since the render surfaces could have expanded, they should not clip (their + // bounds would no longer be reliable). We should resort to layer clipping + // in this case. + EXPECT_EQ(gfx::Rect(0, 0, 0, 0).ToString(), + render_surface1->render_surface()->clip_rect().ToString()); + EXPECT_FALSE(render_surface1->render_surface()->is_clipped()); + EXPECT_EQ(gfx::Rect(0, 0, 0, 0).ToString(), + render_surface2->render_surface()->clip_rect().ToString()); + EXPECT_FALSE(render_surface2->render_surface()->is_clipped()); + + // NB: clip rects are in target space. + EXPECT_EQ(gfx::Rect(0, 0, 40, 40).ToString(), + render_surface1->clip_rect().ToString()); + EXPECT_TRUE(render_surface1->is_clipped()); + + // This value is inherited from the clipping ancestor layer, 'intervening'. + EXPECT_EQ(gfx::Rect(0, 0, 5, 5).ToString(), + render_surface2->clip_rect().ToString()); + EXPECT_TRUE(render_surface2->is_clipped()); + + // The content rects of both render surfaces should both have expanded to + // contain the clip child. + EXPECT_EQ(gfx::Rect(0, 0, 40, 40).ToString(), + render_surface1->render_surface()->content_rect().ToString()); + EXPECT_EQ(gfx::Rect(-1, -1, 40, 40).ToString(), + render_surface2->render_surface()->content_rect().ToString()); + + // The clip child should have inherited the clip parent's clip (projected to + // the right space, of course), and should have the correctly sized visible + // content rect. + EXPECT_EQ(gfx::Rect(-1, -1, 40, 40).ToString(), + clip_child->clip_rect().ToString()); + EXPECT_EQ(gfx::Rect(9, 9, 40, 40).ToString(), + clip_child->visible_content_rect().ToString()); + EXPECT_TRUE(clip_child->is_clipped()); +} + +TEST_F(LayerTreeHostCommonTest, ClipParentScrolledInterveningLayer) { + // Ensure that intervening render surfaces are not a problem, even if there + // is a scroll involved. Note, we do _not_ have to consider any other sort + // of transform. + // + // root (a render surface) + // + clip_parent (masks to bounds) + // + render_surface1 (sets opacity) + // + intervening (masks to bounds AND scrolls) + // + render_surface2 (also sets opacity) + // + clip_child + // + scoped_refptr<Layer> root = Layer::Create(); + scoped_refptr<Layer> clip_parent = Layer::Create(); + scoped_refptr<Layer> render_surface1 = Layer::Create(); + scoped_refptr<Layer> intervening = Layer::Create(); + scoped_refptr<Layer> render_surface2 = Layer::Create(); + scoped_refptr<LayerWithForcedDrawsContent> clip_child = + make_scoped_refptr(new LayerWithForcedDrawsContent); + + root->AddChild(clip_parent); + clip_parent->AddChild(render_surface1); + render_surface1->AddChild(intervening); + intervening->AddChild(render_surface2); + render_surface2->AddChild(clip_child); + + clip_child->SetClipParent(clip_parent.get()); + + intervening->SetMasksToBounds(true); + clip_parent->SetMasksToBounds(true); + intervening->SetScrollable(true); + intervening->SetMaxScrollOffset(gfx::Vector2d(50, 50)); + intervening->SetScrollOffset(gfx::Vector2d(3, 3)); + + render_surface1->SetForceRenderSurface(true); + render_surface2->SetForceRenderSurface(true); + + gfx::Transform translation_transform; + translation_transform.Translate(2, 2); + + gfx::Transform identity_transform; + SetLayerPropertiesForTesting(root.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(50, 50), + false); + SetLayerPropertiesForTesting(clip_parent.get(), + translation_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(1.f, 1.f), + gfx::Size(40, 40), + false); + SetLayerPropertiesForTesting(render_surface1.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(10, 10), + false); + SetLayerPropertiesForTesting(intervening.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(1.f, 1.f), + gfx::Size(5, 5), + false); + SetLayerPropertiesForTesting(render_surface2.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(10, 10), + false); + SetLayerPropertiesForTesting(clip_child.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(-10.f, -10.f), + gfx::Size(60, 60), + false); + + scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create(); + host->SetRootLayer(root); + + ExecuteCalculateDrawProperties(root.get()); + + EXPECT_TRUE(root->render_surface()); + EXPECT_TRUE(render_surface1->render_surface()); + EXPECT_TRUE(render_surface2->render_surface()); + + // Since the render surfaces could have expanded, they should not clip (their + // bounds would no longer be reliable). We should resort to layer clipping + // in this case. + EXPECT_EQ(gfx::Rect(0, 0, 0, 0).ToString(), + render_surface1->render_surface()->clip_rect().ToString()); + EXPECT_FALSE(render_surface1->render_surface()->is_clipped()); + EXPECT_EQ(gfx::Rect(0, 0, 0, 0).ToString(), + render_surface2->render_surface()->clip_rect().ToString()); + EXPECT_FALSE(render_surface2->render_surface()->is_clipped()); + + // NB: clip rects are in target space. + EXPECT_EQ(gfx::Rect(0, 0, 40, 40).ToString(), + render_surface1->clip_rect().ToString()); + EXPECT_TRUE(render_surface1->is_clipped()); + + // This value is inherited from the clipping ancestor layer, 'intervening'. + EXPECT_EQ(gfx::Rect(2, 2, 3, 3).ToString(), + render_surface2->clip_rect().ToString()); + EXPECT_TRUE(render_surface2->is_clipped()); + + // The content rects of both render surfaces should both have expanded to + // contain the clip child. + EXPECT_EQ(gfx::Rect(0, 0, 40, 40).ToString(), + render_surface1->render_surface()->content_rect().ToString()); + EXPECT_EQ(gfx::Rect(2, 2, 40, 40).ToString(), + render_surface2->render_surface()->content_rect().ToString()); + + // The clip child should have inherited the clip parent's clip (projected to + // the right space, of course), and should have the correctly sized visible + // content rect. + EXPECT_EQ(gfx::Rect(2, 2, 40, 40).ToString(), + clip_child->clip_rect().ToString()); + EXPECT_EQ(gfx::Rect(12, 12, 40, 40).ToString(), + clip_child->visible_content_rect().ToString()); + EXPECT_TRUE(clip_child->is_clipped()); +} + +TEST_F(LayerTreeHostCommonTest, DescendantsOfClipChildren) { + // Ensures that descendants of the clip child inherit the correct clip. + // + // root (a render surface) + // + clip_parent (masks to bounds) + // + intervening (masks to bounds) + // + clip_child + // + child + // + scoped_refptr<Layer> root = Layer::Create(); + scoped_refptr<Layer> clip_parent = Layer::Create(); + scoped_refptr<Layer> intervening = Layer::Create(); + scoped_refptr<Layer> clip_child = Layer::Create(); + scoped_refptr<LayerWithForcedDrawsContent> child = + make_scoped_refptr(new LayerWithForcedDrawsContent); + + root->AddChild(clip_parent); + clip_parent->AddChild(intervening); + intervening->AddChild(clip_child); + clip_child->AddChild(child); + + clip_child->SetClipParent(clip_parent.get()); + + intervening->SetMasksToBounds(true); + clip_parent->SetMasksToBounds(true); + + gfx::Transform identity_transform; + SetLayerPropertiesForTesting(root.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(50, 50), + false); + SetLayerPropertiesForTesting(clip_parent.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(40, 40), + false); + SetLayerPropertiesForTesting(intervening.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(5, 5), + false); + SetLayerPropertiesForTesting(clip_child.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(60, 60), + false); + SetLayerPropertiesForTesting(child.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(60, 60), + false); + + scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create(); + host->SetRootLayer(root); + + ExecuteCalculateDrawProperties(root.get()); + + EXPECT_TRUE(root->render_surface()); + + // Neither the clip child nor its descendant should have inherited the clip + // from |intervening|. + EXPECT_EQ(gfx::Rect(0, 0, 40, 40).ToString(), + clip_child->clip_rect().ToString()); + EXPECT_TRUE(clip_child->is_clipped()); + EXPECT_EQ(gfx::Rect(0, 0, 40, 40).ToString(), + child->visible_content_rect().ToString()); + EXPECT_TRUE(child->is_clipped()); +} + +TEST_F(LayerTreeHostCommonTest, + SurfacesShouldBeUnaffectedByNonDescendantClipChildren) { + // Ensures that non-descendant clip children in the tree do not affect + // render surfaces. + // + // root (a render surface) + // + clip_parent (masks to bounds) + // + render_surface1 + // + clip_child + // + render_surface2 + // + non_clip_child + // + // In this example render_surface2 should be unaffected by clip_child. + scoped_refptr<Layer> root = Layer::Create(); + scoped_refptr<Layer> clip_parent = Layer::Create(); + scoped_refptr<Layer> render_surface1 = Layer::Create(); + scoped_refptr<LayerWithForcedDrawsContent> clip_child = + make_scoped_refptr(new LayerWithForcedDrawsContent); + scoped_refptr<Layer> render_surface2 = Layer::Create(); + scoped_refptr<LayerWithForcedDrawsContent> non_clip_child = + make_scoped_refptr(new LayerWithForcedDrawsContent); + + root->AddChild(clip_parent); + clip_parent->AddChild(render_surface1); + render_surface1->AddChild(clip_child); + clip_parent->AddChild(render_surface2); + render_surface2->AddChild(non_clip_child); + + clip_child->SetClipParent(clip_parent.get()); + + clip_parent->SetMasksToBounds(true); + render_surface1->SetMasksToBounds(true); + + gfx::Transform identity_transform; + SetLayerPropertiesForTesting(root.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(15, 15), + false); + SetLayerPropertiesForTesting(clip_parent.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(10, 10), + false); + SetLayerPropertiesForTesting(render_surface1.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(5, 5), + gfx::Size(5, 5), + false); + SetLayerPropertiesForTesting(render_surface2.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(5, 5), + false); + SetLayerPropertiesForTesting(clip_child.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(-1, 1), + gfx::Size(10, 10), + false); + SetLayerPropertiesForTesting(non_clip_child.get(), + identity_transform, + identity_transform, + gfx::PointF(), + gfx::PointF(), + gfx::Size(5, 5), + false); + + render_surface1->SetForceRenderSurface(true); + render_surface2->SetForceRenderSurface(true); + + scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create(); + host->SetRootLayer(root); + + ExecuteCalculateDrawProperties(root.get()); + + EXPECT_TRUE(root->render_surface()); + EXPECT_TRUE(render_surface1->render_surface()); + EXPECT_TRUE(render_surface2->render_surface()); + + EXPECT_EQ(gfx::Rect(0, 0, 5, 5).ToString(), + render_surface1->clip_rect().ToString()); + EXPECT_TRUE(render_surface1->is_clipped()); + + // The render surface should not clip (it has unclipped descendants), instead + // it should rely on layer clipping. + EXPECT_EQ(gfx::Rect(0, 0, 0, 0).ToString(), + render_surface1->render_surface()->clip_rect().ToString()); + EXPECT_FALSE(render_surface1->render_surface()->is_clipped()); + + // That said, it should have grown to accomodate the unclipped descendant. + EXPECT_EQ(gfx::Rect(-1, 1, 6, 4).ToString(), + render_surface1->render_surface()->content_rect().ToString()); + + // This render surface should clip. It has no unclipped descendants. + EXPECT_EQ(gfx::Rect(0, 0, 5, 5).ToString(), + render_surface2->clip_rect().ToString()); + EXPECT_TRUE(render_surface2->render_surface()->is_clipped()); + + // It also shouldn't have grown to accomodate the clip child. + EXPECT_EQ(gfx::Rect(0, 0, 5, 5).ToString(), + render_surface2->render_surface()->content_rect().ToString()); + + // Sanity check our num_unclipped_descendants values. + EXPECT_EQ(1, render_surface1->num_unclipped_descendants()); + EXPECT_EQ(0, render_surface2->num_unclipped_descendants()); +} + } // namespace } // namespace cc diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc index 85f1a22..1131050 100644 --- a/cc/trees/layer_tree_host_impl.cc +++ b/cc/trees/layer_tree_host_impl.cc @@ -1815,6 +1815,12 @@ void LayerTreeHostImpl::BindToClient(InputHandlerClient* client) { input_handler_client_ = client; } +static LayerImpl* NextScrollLayer(LayerImpl* layer) { + if (LayerImpl* scroll_parent = layer->scroll_parent()) + return scroll_parent; + return layer->parent(); +} + InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin( gfx::Point viewport_point, InputHandler::ScrollInputType type) { TRACE_EVENT0("cc", "LayerTreeHostImpl::ScrollBegin"); @@ -1838,7 +1844,7 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin( // Walk up the hierarchy and look for a scrollable layer. LayerImpl* potentially_scrolling_layer_impl = 0; - for (; layer_impl; layer_impl = layer_impl->parent()) { + for (; layer_impl; layer_impl = NextScrollLayer(layer_impl)) { // The content layer can also block attempts to scroll outside the main // thread. ScrollStatus status = layer_impl->TryScroll(device_viewport_point, type); |