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