blob: 4cd7d2d38b8c6f4f876a1bb5dc3ae9c2c2350f78 [file] [log] [blame]
/*
* Copyright (C) 2009, 2010 Apple Inc. All rights reserved.
* Copyright (C) 2014 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/layout/compositing/CompositingRequirementsUpdater.h"
#include "core/layout/LayoutView.h"
#include "core/layout/compositing/PaintLayerCompositor.h"
#include "core/paint/PaintLayer.h"
#include "core/paint/PaintLayerStackingNode.h"
#include "core/paint/PaintLayerStackingNodeIterator.h"
#include "platform/TraceEvent.h"
namespace blink {
class OverlapMapContainer {
public:
void add(const IntRect& bounds) {
m_layerRects.append(bounds);
m_boundingBox.unite(bounds);
}
bool overlapsLayers(const IntRect& bounds) const {
// Checking with the bounding box will quickly reject cases when
// layers are created for lists of items going in one direction and
// never overlap with each other.
if (!bounds.intersects(m_boundingBox))
return false;
for (unsigned i = 0; i < m_layerRects.size(); i++) {
if (m_layerRects[i].intersects(bounds))
return true;
}
return false;
}
void unite(const OverlapMapContainer& otherContainer) {
m_layerRects.appendVector(otherContainer.m_layerRects);
m_boundingBox.unite(otherContainer.m_boundingBox);
}
private:
Vector<IntRect, 64> m_layerRects;
IntRect m_boundingBox;
};
class CompositingRequirementsUpdater::OverlapMap {
WTF_MAKE_NONCOPYABLE(OverlapMap);
public:
OverlapMap() {
// Begin by assuming the root layer will be composited so that there
// is something on the stack. The root layer should also never get a
// finishCurrentOverlapTestingContext() call.
beginNewOverlapTestingContext();
}
void add(PaintLayer* layer, const IntRect& bounds) {
ASSERT(!layer->isRootLayer());
if (bounds.isEmpty())
return;
// Layers do not contribute to overlap immediately--instead, they will
// contribute to overlap as soon as they have been recursively processed
// and popped off the stack.
ASSERT(m_overlapStack.size() >= 2);
m_overlapStack[m_overlapStack.size() - 2].add(bounds);
}
bool overlapsLayers(const IntRect& bounds) const {
return m_overlapStack.last().overlapsLayers(bounds);
}
void beginNewOverlapTestingContext() {
// This effectively creates a new "clean slate" for overlap state.
// This is used when we know that a subtree or remaining set of
// siblings does not need to check overlap with things behind it.
m_overlapStack.append(OverlapMapContainer());
}
void finishCurrentOverlapTestingContext() {
// The overlap information on the top of the stack is still necessary
// for checking overlap of any layers outside this context that may
// overlap things from inside this context. Therefore, we must merge
// the information from the top of the stack before popping the stack.
//
// FIXME: we may be able to avoid this deep copy by rearranging how
// overlapMap state is managed.
m_overlapStack[m_overlapStack.size() - 2].unite(m_overlapStack.last());
m_overlapStack.removeLast();
}
private:
Vector<OverlapMapContainer> m_overlapStack;
};
class CompositingRequirementsUpdater::RecursionData {
public:
explicit RecursionData(PaintLayer* compositingAncestor)
: m_compositingAncestor(compositingAncestor),
m_subtreeIsCompositing(false),
m_hasUnisolatedCompositedBlendingDescendant(false),
m_testingOverlap(true),
m_hasCompositedScrollingAncestor(false) {}
PaintLayer* m_compositingAncestor;
bool m_subtreeIsCompositing;
bool m_hasUnisolatedCompositedBlendingDescendant;
bool m_testingOverlap;
bool m_hasCompositedScrollingAncestor;
};
static bool requiresCompositingOrSquashing(CompositingReasons reasons) {
#if ENABLE(ASSERT)
bool fastAnswer = reasons != CompositingReasonNone;
bool slowAnswer = requiresCompositing(reasons) || requiresSquashing(reasons);
ASSERT(fastAnswer == slowAnswer);
#endif
return reasons != CompositingReasonNone;
}
static CompositingReasons subtreeReasonsForCompositing(
PaintLayer* layer,
bool hasCompositedDescendants,
bool has3DTransformedDescendants) {
CompositingReasons subtreeReasons = CompositingReasonNone;
// When a layer has composited descendants, some effects, like 2d transforms,
// filters, masks etc must be implemented via compositing so that they also
// apply to those composited descendants.
if (hasCompositedDescendants) {
subtreeReasons |= layer->potentialCompositingReasonsFromStyle() &
CompositingReasonComboCompositedDescendants;
if (layer->shouldIsolateCompositedDescendants()) {
ASSERT(layer->stackingNode()->isStackingContext());
subtreeReasons |= CompositingReasonIsolateCompositedDescendants;
}
// FIXME: This should move into
// CompositingReasonFinder::potentialCompositingReasonsFromStyle, but theres
// a poor interaction with LayoutTextControlSingleLine, which sets this
// hasOverflowClip directly.
if (layer->layoutObject()->hasClipRelatedProperty())
subtreeReasons |= CompositingReasonClipsCompositingDescendants;
if (layer->layoutObject()->style()->position() == FixedPosition)
subtreeReasons |= CompositingReasonPositionFixedWithCompositedDescendants;
}
// A layer with preserve-3d or perspective only needs to be composited if
// there are descendant layers that will be affected by the preserve-3d or
// perspective.
if (has3DTransformedDescendants)
subtreeReasons |= layer->potentialCompositingReasonsFromStyle() &
CompositingReasonCombo3DDescendants;
return subtreeReasons;
}
CompositingRequirementsUpdater::CompositingRequirementsUpdater(
LayoutView& layoutView,
CompositingReasonFinder& compositingReasonFinder)
: m_layoutView(layoutView),
m_compositingReasonFinder(compositingReasonFinder) {}
CompositingRequirementsUpdater::~CompositingRequirementsUpdater() {}
void CompositingRequirementsUpdater::update(PaintLayer* root) {
TRACE_EVENT0("blink", "CompositingRequirementsUpdater::updateRecursive");
// Go through the layers in presentation order, so that we can compute which
// Layers need compositing layers.
// FIXME: we could maybe do this and the hierarchy update in one pass, but the
// parenting logic would be more complex.
RecursionData recursionData(root);
OverlapMap overlapTestRequestMap;
bool saw3DTransform = false;
// FIXME: Passing these unclippedDescendants down and keeping track
// of them dynamically, we are requiring a full tree walk. This
// should be removed as soon as proper overlap testing based on
// scrolling and animation bounds is implemented (crbug.com/252472).
Vector<PaintLayer*> unclippedDescendants;
IntRect absoluteDescendantBoundingBox;
updateRecursive(0, root, overlapTestRequestMap, recursionData, saw3DTransform,
unclippedDescendants, absoluteDescendantBoundingBox);
}
void CompositingRequirementsUpdater::updateRecursive(
PaintLayer* ancestorLayer,
PaintLayer* layer,
OverlapMap& overlapMap,
RecursionData& currentRecursionData,
bool& descendantHas3DTransform,
Vector<PaintLayer*>& unclippedDescendants,
IntRect& absoluteDescendantBoundingBox) {
PaintLayerCompositor* compositor = m_layoutView.compositor();
layer->stackingNode()->updateLayerListsIfNeeded();
CompositingReasons reasonsToComposite = CompositingReasonNone;
CompositingReasons directReasons =
m_compositingReasonFinder.directReasons(layer);
// Video is special. It's the only PaintLayer type that can both have
// PaintLayer children and whose children can't use its backing to render
// into. These children (the controls) always need to be promoted into their
// own layers to draw on top of the accelerated video.
if (currentRecursionData.m_compositingAncestor &&
currentRecursionData.m_compositingAncestor->layoutObject()->isVideo())
directReasons |= CompositingReasonVideoOverlay;
if (currentRecursionData.m_hasCompositedScrollingAncestor &&
layer->layoutObject()->styleRef().hasViewportConstrainedPosition())
directReasons |= CompositingReasonScrollDependentPosition;
bool canBeComposited = compositor->canBeComposited(layer);
if (canBeComposited) {
reasonsToComposite |= directReasons;
if (layer->isRootLayer() && compositor->rootShouldAlwaysComposite())
reasonsToComposite |= CompositingReasonRoot;
// Add CompositingReasonOverflowScrollingTouch for layers that do not
// already have it but need it.
// Note that m_compositingReasonFinder.directReasons(layer) already includes
// CompositingReasonOverflowScrollingTouch for anything that has
// layer->needsCompositedScrolling() true. That is, for cases where we
// explicitly decide not to have LCD text or cases where the layer will
// still support LCD text even if the layer is composited.
if (reasonsToComposite && layer->scrollsOverflow() &&
!layer->needsCompositedScrolling()) {
// We can get here for a scroller that will be composited for some other
// reason and hence will already use grayscale AA text. We recheck for
// needsCompositedScrolling ignoring LCD to correctly add the
// CompositingReasonOverflowScrollingTouch reason to layers that can
// support it with grayscale AA text.
layer->getScrollableArea()->updateNeedsCompositedScrolling(
PaintLayerScrollableArea::IgnoreLCDText);
if (layer->needsCompositedScrolling())
reasonsToComposite |= CompositingReasonOverflowScrollingTouch;
}
}
if ((reasonsToComposite & CompositingReasonOverflowScrollingTouch) &&
!layer->isRootLayer())
currentRecursionData.m_hasCompositedScrollingAncestor = true;
// Next, accumulate reasons related to overlap.
// If overlap testing is used, this reason will be overridden. If overlap
// testing is not used, we must assume we overlap if there is anything
// composited behind us in paint-order.
CompositingReasons overlapCompositingReason =
currentRecursionData.m_subtreeIsCompositing
? CompositingReasonAssumedOverlap
: CompositingReasonNone;
if (currentRecursionData.m_hasCompositedScrollingAncestor) {
Vector<size_t> unclippedDescendantsToRemove;
for (size_t i = 0; i < unclippedDescendants.size(); i++) {
PaintLayer* unclippedDescendant = unclippedDescendants.at(i);
// If we've reached the containing block of one of the unclipped
// descendants, that element is no longer relevant to whether or not we
// should opt in. Unfortunately we can't easily remove from the list
// while we're iterating, so we have to store it for later removal.
if (unclippedDescendant->layoutObject()->containingBlock() ==
layer->layoutObject()) {
unclippedDescendantsToRemove.append(i);
continue;
}
if (layer->scrollsWithRespectTo(unclippedDescendant))
reasonsToComposite |= CompositingReasonAssumedOverlap;
}
// Remove irrelevant unclipped descendants in reverse order so our stored
// indices remain valid.
for (size_t i = 0; i < unclippedDescendantsToRemove.size(); i++)
unclippedDescendants.remove(unclippedDescendantsToRemove.at(
unclippedDescendantsToRemove.size() - i - 1));
if (layer->clipParent()) {
// TODO(schenney): We only need to promote when the clipParent is not a
// descendant of the ancestor scroller, which we do not check for here.
// Hence we might be promoting needlessly.
unclippedDescendants.append(layer);
}
}
const IntRect& absBounds = layer->clippedAbsoluteBoundingBox();
absoluteDescendantBoundingBox = absBounds;
if (currentRecursionData.m_testingOverlap &&
!requiresCompositingOrSquashing(directReasons))
overlapCompositingReason = overlapMap.overlapsLayers(absBounds)
? CompositingReasonOverlap
: CompositingReasonNone;
reasonsToComposite |= overlapCompositingReason;
// The children of this layer don't need to composite, unless there is
// a compositing layer among them, so start by inheriting the compositing
// ancestor with m_subtreeIsCompositing set to false.
RecursionData childRecursionData = currentRecursionData;
childRecursionData.m_subtreeIsCompositing = false;
bool willBeCompositedOrSquashed =
canBeComposited && requiresCompositingOrSquashing(reasonsToComposite);
if (willBeCompositedOrSquashed) {
// This layer now acts as the ancestor for kids.
childRecursionData.m_compositingAncestor = layer;
// Here we know that all children and the layer's own contents can blindly
// paint into this layer's backing, until a descendant is composited. So, we
// don't need to check for overlap with anything behind this layer.
overlapMap.beginNewOverlapTestingContext();
// This layer is going to be composited, so children can safely ignore the
// fact that there's an animation running behind this layer, meaning they
// can rely on the overlap map testing again.
childRecursionData.m_testingOverlap = true;
}
#if ENABLE(ASSERT)
LayerListMutationDetector mutationChecker(layer->stackingNode());
#endif
bool anyDescendantHas3DTransform = false;
bool willHaveForegroundLayer = false;
if (layer->stackingNode()->isStackingContext()) {
PaintLayerStackingNodeIterator iterator(*layer->stackingNode(),
NegativeZOrderChildren);
while (PaintLayerStackingNode* curNode = iterator.next()) {
IntRect absoluteChildDescendantBoundingBox;
updateRecursive(layer, curNode->layer(), overlapMap, childRecursionData,
anyDescendantHas3DTransform, unclippedDescendants,
absoluteChildDescendantBoundingBox);
absoluteDescendantBoundingBox.unite(absoluteChildDescendantBoundingBox);
// If we have to make a layer for this child, make one now so we can have
// a contents layer (since we need to ensure that the -ve z-order child
// renders underneath our contents).
if (childRecursionData.m_subtreeIsCompositing) {
reasonsToComposite |= CompositingReasonNegativeZIndexChildren;
if (!willBeCompositedOrSquashed) {
// make layer compositing
childRecursionData.m_compositingAncestor = layer;
overlapMap.beginNewOverlapTestingContext();
willBeCompositedOrSquashed = true;
willHaveForegroundLayer = true;
// FIXME: temporary solution for the first negative z-index composited
// child: re-compute the absBounds for the child so that we can add
// the negative z-index child's bounds to the new overlap context.
overlapMap.beginNewOverlapTestingContext();
overlapMap.add(curNode->layer(),
curNode->layer()->clippedAbsoluteBoundingBox());
overlapMap.finishCurrentOverlapTestingContext();
}
}
}
}
if (willHaveForegroundLayer) {
ASSERT(willBeCompositedOrSquashed);
// A foreground layer effectively is a new backing for all subsequent
// children, so we don't need to test for overlap with anything behind this.
// So, we can finish the previous context that was accumulating rects for
// the negative z-index children, and start with a fresh new empty context.
overlapMap.finishCurrentOverlapTestingContext();
overlapMap.beginNewOverlapTestingContext();
// This layer is going to be composited, so children can safely ignore the
// fact that there's an animation running behind this layer, meaning they
// can rely on the overlap map testing again.
childRecursionData.m_testingOverlap = true;
}
PaintLayerStackingNodeIterator iterator(
*layer->stackingNode(), NormalFlowChildren | PositiveZOrderChildren);
while (PaintLayerStackingNode* curNode = iterator.next()) {
IntRect absoluteChildDescendantBoundingBox;
updateRecursive(layer, curNode->layer(), overlapMap, childRecursionData,
anyDescendantHas3DTransform, unclippedDescendants,
absoluteChildDescendantBoundingBox);
absoluteDescendantBoundingBox.unite(absoluteChildDescendantBoundingBox);
}
// Now that the subtree has been traversed, we can check for compositing
// reasons that depended on the state of the subtree.
if (layer->stackingNode()->isStackingContext()) {
layer->setShouldIsolateCompositedDescendants(
childRecursionData.m_hasUnisolatedCompositedBlendingDescendant);
} else {
layer->setShouldIsolateCompositedDescendants(false);
currentRecursionData.m_hasUnisolatedCompositedBlendingDescendant =
childRecursionData.m_hasUnisolatedCompositedBlendingDescendant;
}
// Subsequent layers in the parent's stacking context may also need to
// composite.
if (childRecursionData.m_subtreeIsCompositing)
currentRecursionData.m_subtreeIsCompositing = true;
// Set the flag to say that this SC has compositing children.
layer->setHasCompositingDescendant(childRecursionData.m_subtreeIsCompositing);
if (layer->isRootLayer()) {
// The root layer needs to be composited if anything else in the tree is
// composited. Otherwise, we can disable compositing entirely.
if (childRecursionData.m_subtreeIsCompositing ||
requiresCompositingOrSquashing(reasonsToComposite) ||
compositor->rootShouldAlwaysComposite()) {
reasonsToComposite |= CompositingReasonRoot;
currentRecursionData.m_subtreeIsCompositing = true;
} else {
compositor->setCompositingModeEnabled(false);
reasonsToComposite = CompositingReasonNone;
}
} else {
// All layers (even ones that aren't being composited) need to get added to
// the overlap map. Layers that are not separately composited will paint
// into their compositing ancestor's backing, and so are still considered
// for overlap.
if (childRecursionData.m_compositingAncestor &&
!childRecursionData.m_compositingAncestor->isRootLayer())
overlapMap.add(layer, absBounds);
// Now check for reasons to become composited that depend on the state of
// descendant layers.
CompositingReasons subtreeCompositingReasons = subtreeReasonsForCompositing(
layer, childRecursionData.m_subtreeIsCompositing,
anyDescendantHas3DTransform);
reasonsToComposite |= subtreeCompositingReasons;
if (!willBeCompositedOrSquashed && canBeComposited &&
requiresCompositingOrSquashing(subtreeCompositingReasons)) {
childRecursionData.m_compositingAncestor = layer;
// FIXME: this context push is effectively a no-op but needs to exist for
// now, because the code is designed to push overlap information to the
// second-from-top context of the stack.
overlapMap.beginNewOverlapTestingContext();
overlapMap.add(layer, absoluteDescendantBoundingBox);
willBeCompositedOrSquashed = true;
}
if (willBeCompositedOrSquashed)
reasonsToComposite |= layer->potentialCompositingReasonsFromStyle() &
CompositingReasonInlineTransform;
if (willBeCompositedOrSquashed &&
layer->layoutObject()->style()->hasBlendMode())
currentRecursionData.m_hasUnisolatedCompositedBlendingDescendant = true;
// Tell the parent it has compositing descendants.
if (willBeCompositedOrSquashed)
currentRecursionData.m_subtreeIsCompositing = true;
// Turn overlap testing off for later layers if it's already off, or if we
// have an animating transform. Note that if the layer clips its
// descendants, there's no reason to propagate the child animation to the
// parent layers. That's because we know for sure the animation is contained
// inside the clipping rectangle, which is already added to the overlap map.
bool isCompositedClippingLayer =
canBeComposited &&
(reasonsToComposite & CompositingReasonClipsCompositingDescendants);
bool isCompositedWithInlineTransform =
reasonsToComposite & CompositingReasonInlineTransform;
if ((!childRecursionData.m_testingOverlap && !isCompositedClippingLayer) ||
layer->layoutObject()->style()->hasCurrentTransformAnimation() ||
isCompositedWithInlineTransform)
currentRecursionData.m_testingOverlap = false;
if (childRecursionData.m_compositingAncestor == layer)
overlapMap.finishCurrentOverlapTestingContext();
descendantHas3DTransform |=
anyDescendantHas3DTransform || layer->has3DTransform();
}
// At this point we have finished collecting all reasons to composite this
// layer.
layer->setCompositingReasons(reasonsToComposite);
}
} // namespace blink