diff -r 000000000000 -r 6474c204b198 gfx/layers/LayerTreeInvalidation.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gfx/layers/LayerTreeInvalidation.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,461 @@ +/*-*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LayerTreeInvalidation.h" +#include // for uint32_t +#include "ImageContainer.h" // for ImageContainer +#include "ImageLayers.h" // for ImageLayer, etc +#include "Layers.h" // for Layer, ContainerLayer, etc +#include "gfx3DMatrix.h" // for gfx3DMatrix +#include "gfxColor.h" // for gfxRGBA +#include "GraphicsFilter.h" // for GraphicsFilter +#include "gfxPoint3D.h" // for gfxPoint3D +#include "gfxRect.h" // for gfxRect +#include "gfxUtils.h" // for gfxUtils +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/mozalloc.h" // for operator new, etc +#include "nsAutoPtr.h" // for nsRefPtr, nsAutoPtr, etc +#include "nsDataHashtable.h" // for nsDataHashtable +#include "nsDebug.h" // for NS_ASSERTION +#include "nsHashKeys.h" // for nsPtrHashKey +#include "nsISupportsImpl.h" // for Layer::AddRef, etc +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for nsIntRect +#include "nsTArray.h" // for nsAutoTArray, nsTArray_Impl + +namespace mozilla { +namespace layers { + +struct LayerPropertiesBase; +LayerPropertiesBase* CloneLayerTreePropertiesInternal(Layer* aRoot); + +static nsIntRect +TransformRect(const nsIntRect& aRect, const gfx3DMatrix& aTransform) +{ + if (aRect.IsEmpty()) { + return nsIntRect(); + } + + gfxRect rect(aRect.x, aRect.y, aRect.width, aRect.height); + rect = aTransform.TransformBounds(rect); + rect.RoundOut(); + + nsIntRect intRect; + if (!gfxUtils::GfxRectToIntRect(rect, &intRect)) { + return nsIntRect(); + } + + return intRect; +} + +static void +AddTransformedRegion(nsIntRegion& aDest, const nsIntRegion& aSource, const gfx3DMatrix& aTransform) +{ + nsIntRegionRectIterator iter(aSource); + const nsIntRect *r; + while ((r = iter.Next())) { + aDest.Or(aDest, TransformRect(*r, aTransform)); + } + aDest.SimplifyOutward(20); +} + +static void +AddRegion(nsIntRegion& aDest, const nsIntRegion& aSource) +{ + aDest.Or(aDest, aSource); + aDest.SimplifyOutward(20); +} + +static nsIntRegion +TransformRegion(const nsIntRegion& aRegion, const gfx3DMatrix& aTransform) +{ + nsIntRegion result; + AddTransformedRegion(result, aRegion, aTransform); + return result; +} + +/** + * Walks over this layer, and all descendant layers. + * If any of these are a ContainerLayer that reports invalidations to a PresShell, + * then report that the entire bounds have changed. + */ +static void +NotifySubdocumentInvalidationRecursive(Layer* aLayer, NotifySubDocInvalidationFunc aCallback) +{ + aLayer->ClearInvalidRect(); + ContainerLayer* container = aLayer->AsContainerLayer(); + + if (aLayer->GetMaskLayer()) { + NotifySubdocumentInvalidationRecursive(aLayer->GetMaskLayer(), aCallback); + } + + if (!container) { + return; + } + + for (Layer* child = container->GetFirstChild(); child; child = child->GetNextSibling()) { + NotifySubdocumentInvalidationRecursive(child, aCallback); + } + + aCallback(container, container->GetVisibleRegion()); +} + +struct LayerPropertiesBase : public LayerProperties +{ + LayerPropertiesBase(Layer* aLayer) + : mLayer(aLayer) + , mMaskLayer(nullptr) + , mVisibleRegion(aLayer->GetVisibleRegion()) + , mInvalidRegion(aLayer->GetInvalidRegion()) + , mOpacity(aLayer->GetLocalOpacity()) + , mUseClipRect(!!aLayer->GetClipRect()) + { + MOZ_COUNT_CTOR(LayerPropertiesBase); + if (aLayer->GetMaskLayer()) { + mMaskLayer = CloneLayerTreePropertiesInternal(aLayer->GetMaskLayer()); + } + if (mUseClipRect) { + mClipRect = *aLayer->GetClipRect(); + } + gfx::To3DMatrix(aLayer->GetTransform(), mTransform); + } + LayerPropertiesBase() + : mLayer(nullptr) + , mMaskLayer(nullptr) + { + MOZ_COUNT_CTOR(LayerPropertiesBase); + } + ~LayerPropertiesBase() + { + MOZ_COUNT_DTOR(LayerPropertiesBase); + } + + virtual nsIntRegion ComputeDifferences(Layer* aRoot, + NotifySubDocInvalidationFunc aCallback, + bool* aGeometryChanged); + + virtual void MoveBy(const nsIntPoint& aOffset); + + nsIntRegion ComputeChange(NotifySubDocInvalidationFunc aCallback, + bool& aGeometryChanged) + { + gfx3DMatrix transform; + gfx::To3DMatrix(mLayer->GetTransform(), transform); + bool transformChanged = !mTransform.FuzzyEqual(transform); + Layer* otherMask = mLayer->GetMaskLayer(); + const nsIntRect* otherClip = mLayer->GetClipRect(); + nsIntRegion result; + if ((mMaskLayer ? mMaskLayer->mLayer : nullptr) != otherMask || + (mUseClipRect != !!otherClip) || + mLayer->GetLocalOpacity() != mOpacity || + transformChanged) + { + aGeometryChanged = true; + result = OldTransformedBounds(); + if (transformChanged) { + AddRegion(result, NewTransformedBounds()); + } + + // If we don't have to generate invalidations separately for child + // layers then we can just stop here since we've already invalidated the entire + // old and new bounds. + if (!aCallback) { + ClearInvalidations(mLayer); + return result; + } + } + + nsIntRegion visible; + visible.Xor(mVisibleRegion, mLayer->GetVisibleRegion()); + if (!visible.IsEmpty()) { + aGeometryChanged = true; + } + AddTransformedRegion(result, visible, mTransform); + + AddRegion(result, ComputeChangeInternal(aCallback, aGeometryChanged)); + AddTransformedRegion(result, mLayer->GetInvalidRegion(), mTransform); + + if (mMaskLayer && otherMask) { + AddTransformedRegion(result, mMaskLayer->ComputeChange(aCallback, aGeometryChanged), + mTransform); + } + + if (mUseClipRect && otherClip) { + if (!mClipRect.IsEqualInterior(*otherClip)) { + nsIntRegion tmp; + tmp.Xor(mClipRect, *otherClip); + AddRegion(result, tmp); + } + } + + mLayer->ClearInvalidRect(); + return result; + } + + nsIntRect NewTransformedBounds() + { + gfx3DMatrix transform; + gfx::To3DMatrix(mLayer->GetTransform(), transform); + return TransformRect(mLayer->GetVisibleRegion().GetBounds(), transform); + } + + nsIntRect OldTransformedBounds() + { + return TransformRect(mVisibleRegion.GetBounds(), mTransform); + } + + virtual nsIntRegion ComputeChangeInternal(NotifySubDocInvalidationFunc aCallback, + bool& aGeometryChanged) + { + return nsIntRect(); + } + + nsRefPtr mLayer; + nsAutoPtr mMaskLayer; + nsIntRegion mVisibleRegion; + nsIntRegion mInvalidRegion; + gfx3DMatrix mTransform; + float mOpacity; + nsIntRect mClipRect; + bool mUseClipRect; +}; + +struct ContainerLayerProperties : public LayerPropertiesBase +{ + ContainerLayerProperties(ContainerLayer* aLayer) + : LayerPropertiesBase(aLayer) + { + for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { + mChildren.AppendElement(CloneLayerTreePropertiesInternal(child)); + } + } + + virtual nsIntRegion ComputeChangeInternal(NotifySubDocInvalidationFunc aCallback, + bool& aGeometryChanged) + { + ContainerLayer* container = mLayer->AsContainerLayer(); + nsIntRegion result; + + // A low frame rate is especially visible to users when scrolling, so we + // particularly want to avoid unnecessary invalidation at that time. For us + // here, that means avoiding unnecessary invalidation of child items when + // other children are added to or removed from our container layer, since + // that may be caused by children being scrolled in or out of view. We are + // less concerned with children changing order. + // TODO: Consider how we could avoid unnecessary invalidation when children + // change order, and whether the overhead would be worth it. + + nsDataHashtable, uint32_t> oldIndexMap(mChildren.Length()); + for (uint32_t i = 0; i < mChildren.Length(); ++i) { + oldIndexMap.Put(mChildren[i]->mLayer, i); + } + + uint32_t i = 0; // cursor into the old child list mChildren + for (Layer* child = container->GetFirstChild(); child; child = child->GetNextSibling()) { + bool invalidateChildsCurrentArea = false; + if (i < mChildren.Length()) { + uint32_t childsOldIndex; + if (oldIndexMap.Get(child, &childsOldIndex)) { + if (childsOldIndex >= i) { + // Invalidate the old areas of layers that used to be between the + // current |child| and the previous |child| that was also in the + // old list mChildren (if any of those children have been reordered + // rather than removed, we will invalidate their new area when we + // encounter them in the new list): + for (uint32_t j = i; j < childsOldIndex; ++j) { + AddRegion(result, mChildren[j]->OldTransformedBounds()); + } + // Invalidate any regions of the child that have changed: + AddRegion(result, mChildren[childsOldIndex]->ComputeChange(aCallback, aGeometryChanged)); + i = childsOldIndex + 1; + } else { + // We've already seen this child in mChildren (which means it must + // have been reordered) and invalidated its old area. We need to + // invalidate its new area too: + invalidateChildsCurrentArea = true; + } + } else { + // |child| is new + invalidateChildsCurrentArea = true; + } + } else { + // |child| is new, or was reordered to a higher index + invalidateChildsCurrentArea = true; + } + if (invalidateChildsCurrentArea) { + gfx3DMatrix transform; + gfx::To3DMatrix(child->GetTransform(), transform); + AddTransformedRegion(result, child->GetVisibleRegion(), transform); + if (aCallback) { + NotifySubdocumentInvalidationRecursive(child, aCallback); + } else { + ClearInvalidations(child); + } + } + } + + // Process remaining removed children. + while (i < mChildren.Length()) { + AddRegion(result, mChildren[i]->OldTransformedBounds()); + i++; + } + + if (aCallback) { + aCallback(container, result); + } + + gfx3DMatrix transform; + gfx::To3DMatrix(mLayer->GetTransform(), transform); + return TransformRegion(result, transform); + } + + // The old list of children: + nsAutoTArray,1> mChildren; +}; + +struct ColorLayerProperties : public LayerPropertiesBase +{ + ColorLayerProperties(ColorLayer *aLayer) + : LayerPropertiesBase(aLayer) + , mColor(aLayer->GetColor()) + { } + + virtual nsIntRegion ComputeChangeInternal(NotifySubDocInvalidationFunc aCallback, + bool& aGeometryChanged) + { + ColorLayer* color = static_cast(mLayer.get()); + + if (mColor != color->GetColor()) { + return NewTransformedBounds(); + } + + return nsIntRegion(); + } + + gfxRGBA mColor; +}; + +struct ImageLayerProperties : public LayerPropertiesBase +{ + ImageLayerProperties(ImageLayer* aImage) + : LayerPropertiesBase(aImage) + , mContainer(aImage->GetContainer()) + , mFilter(aImage->GetFilter()) + , mScaleToSize(aImage->GetScaleToSize()) + , mScaleMode(aImage->GetScaleMode()) + { + } + + virtual nsIntRegion ComputeChangeInternal(NotifySubDocInvalidationFunc aCallback, + bool& aGeometryChanged) + { + ImageLayer* imageLayer = static_cast(mLayer.get()); + + if (!imageLayer->GetVisibleRegion().IsEqual(mVisibleRegion)) { + nsIntRect result = NewTransformedBounds(); + result = result.Union(OldTransformedBounds()); + return result; + } + + if (mContainer != imageLayer->GetContainer() || + mFilter != imageLayer->GetFilter() || + mScaleToSize != imageLayer->GetScaleToSize() || + mScaleMode != imageLayer->GetScaleMode()) { + return NewTransformedBounds(); + } + + return nsIntRect(); + } + + nsRefPtr mContainer; + GraphicsFilter mFilter; + gfx::IntSize mScaleToSize; + ScaleMode mScaleMode; +}; + +LayerPropertiesBase* +CloneLayerTreePropertiesInternal(Layer* aRoot) +{ + if (!aRoot) { + return new LayerPropertiesBase(); + } + + switch (aRoot->GetType()) { + case Layer::TYPE_CONTAINER: + case Layer::TYPE_REF: + return new ContainerLayerProperties(aRoot->AsContainerLayer()); + case Layer::TYPE_COLOR: + return new ColorLayerProperties(static_cast(aRoot)); + case Layer::TYPE_IMAGE: + return new ImageLayerProperties(static_cast(aRoot)); + default: + return new LayerPropertiesBase(aRoot); + } + + return nullptr; +} + +/* static */ LayerProperties* +LayerProperties::CloneFrom(Layer* aRoot) +{ + return CloneLayerTreePropertiesInternal(aRoot); +} + +/* static */ void +LayerProperties::ClearInvalidations(Layer *aLayer) +{ + aLayer->ClearInvalidRect(); + if (aLayer->GetMaskLayer()) { + ClearInvalidations(aLayer->GetMaskLayer()); + } + + ContainerLayer* container = aLayer->AsContainerLayer(); + if (!container) { + return; + } + + for (Layer* child = container->GetFirstChild(); child; child = child->GetNextSibling()) { + ClearInvalidations(child); + } +} + +nsIntRegion +LayerPropertiesBase::ComputeDifferences(Layer* aRoot, NotifySubDocInvalidationFunc aCallback, + bool* aGeometryChanged = nullptr) +{ + NS_ASSERTION(aRoot, "Must have a layer tree to compare against!"); + if (mLayer != aRoot) { + if (aCallback) { + NotifySubdocumentInvalidationRecursive(aRoot, aCallback); + } else { + ClearInvalidations(aRoot); + } + gfx3DMatrix transform; + gfx::To3DMatrix(aRoot->GetTransform(), transform); + nsIntRect result = TransformRect(aRoot->GetVisibleRegion().GetBounds(), transform); + result = result.Union(OldTransformedBounds()); + if (aGeometryChanged != nullptr) { + *aGeometryChanged = true; + } + return result; + } else { + bool geometryChanged = (aGeometryChanged != nullptr) ? *aGeometryChanged : false; + nsIntRegion invalid = ComputeChange(aCallback, geometryChanged); + if (aGeometryChanged != nullptr) { + *aGeometryChanged = geometryChanged; + } + return invalid; + } +} + +void +LayerPropertiesBase::MoveBy(const nsIntPoint& aOffset) +{ + mTransform.TranslatePost(gfxPoint3D(aOffset.x, aOffset.y, 0)); +} + +} // namespace layers +} // namespace mozilla