--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -1271,16 +1271,26 @@ public:
nsIntRegion mRegionToInvalidate;
// The offset between the active scrolled root of this layer
// and the root of the container for the previous and current
// paints respectively.
nsPoint mLastAnimatedGeometryRootOrigin;
nsPoint mAnimatedGeometryRootOrigin;
+ // If mIgnoreInvalidationsOutsideRect is set, this contains the bounds of the
+ // layer's old visible region, in layer pixels.
+ nsIntRect mOldVisibleBounds;
+
+ // If set, invalidations that fall outside of this rect should not result in
+ // calls to layer->InvalidateRegion during DLBI. Instead, the parts outside
+ // this rectangle will be invalidated in InvalidateVisibleBoundsChangesForScrolledLayer.
+ // See the comment in ComputeAndSetIgnoreInvalidationRect for more information.
+ Maybe<nsIntRect> mIgnoreInvalidationsOutsideRect;
+
nsRefPtr<ColorLayer> mColorLayer;
nsRefPtr<ImageLayer> mImageLayer;
};
/*
* User data for layers which will be used as masks.
*/
struct MaskLayerUserData : public LayerUserData
@@ -1447,47 +1457,53 @@ AppendToString(nsACString& s, const nsIn
return s += sfx;
}
/**
* Invalidate aRegion in aLayer. aLayer is in the coordinate system
* *after* aTranslation has been applied, so we need to
* apply the inverse of that transform before calling InvalidateRegion.
*/
-static void
-InvalidatePostTransformRegion(PaintedLayer* aLayer, const nsIntRegion& aRegion,
- const nsIntPoint& aTranslation)
+template<typename RegionOrRect> void
+InvalidatePostTransformRegion(PaintedLayer* aLayer, const RegionOrRect& aRegion,
+ const nsIntPoint& aTranslation,
+ PaintedDisplayItemLayerUserData* aData)
{
// Convert the region from the coordinates of the container layer
// (relative to the snapped top-left of the display list reference frame)
// to the PaintedLayer's own coordinates
- nsIntRegion rgn = aRegion;
+ RegionOrRect rgn = aRegion;
rgn.MoveBy(-aTranslation);
- aLayer->InvalidateRegion(rgn);
+ if (aData->mIgnoreInvalidationsOutsideRect) {
+ rgn = rgn.Intersect(*aData->mIgnoreInvalidationsOutsideRect);
+ }
+ if (!rgn.IsEmpty()) {
+ aLayer->InvalidateRegion(rgn);
#ifdef MOZ_DUMP_PAINTING
- if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
- nsAutoCString str;
- AppendToString(str, rgn);
- printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get());
- }
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ nsAutoCString str;
+ AppendToString(str, rgn);
+ printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get());
+ }
#endif
+ }
}
static void
InvalidatePostTransformRegion(PaintedLayer* aLayer, const nsRect& aRect,
const DisplayItemClip& aClip,
const nsIntPoint& aTranslation)
{
PaintedDisplayItemLayerUserData* data =
static_cast<PaintedDisplayItemLayerUserData*>(aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
nsRect rect = aClip.ApplyNonRoundedIntersection(aRect);
nsIntRect pixelRect = rect.ScaleToOutsidePixels(data->mXScale, data->mYScale, data->mAppUnitsPerDevPixel);
- InvalidatePostTransformRegion(aLayer, pixelRect, aTranslation);
+ InvalidatePostTransformRegion(aLayer, pixelRect, aTranslation, data);
}
static nsIntPoint
GetTranslationForPaintedLayer(PaintedLayer* aLayer)
{
PaintedDisplayItemLayerUserData* data =
static_cast<PaintedDisplayItemLayerUserData*>
@@ -2082,16 +2098,99 @@ ContainerState::RecyclePaintedLayer(Pain
printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get());
}
#endif
data->mRegionToInvalidate.SetEmpty();
}
return data;
}
+static void
+ComputeAndSetIgnoreInvalidationRect(PaintedLayer* aLayer,
+ PaintedDisplayItemLayerUserData* aData,
+ const nsIFrame* aAnimatedGeometryRoot,
+ nsDisplayListBuilder* aBuilder,
+ const nsIntPoint& aLayerTranslation)
+{
+ if (!aLayer->Manager()->IsWidgetLayerManager()) {
+ // This optimization is only useful for layers with retained content.
+ return;
+ }
+
+ const nsIFrame* parentFrame = aAnimatedGeometryRoot->GetParent();
+
+ // GetDirtyRectForScrolledContents will return an empty rect if parentFrame
+ // is not a scrollable frame.
+ nsRect dirtyRect = aBuilder->GetDirtyRectForScrolledContents(parentFrame);
+
+ if (dirtyRect.IsEmpty()) {
+ // parentFrame is not a scrollable frame, or we didn't encounter it during
+ // display list building (though this shouldn't happen), or it's empty.
+ // In all those cases this optimization is not needed.
+ return;
+ }
+
+ // parentFrame is a scrollable frame, and aLayer contains the scrolled
+ // contents of that frame.
+
+ // maxNewVisibleBounds is a conservative approximation of the new visible
+ // region of aLayer.
+ nsIntRect maxNewVisibleBounds =
+ dirtyRect.ScaleToOutsidePixels(aData->mXScale, aData->mYScale,
+ aData->mAppUnitsPerDevPixel) - aLayerTranslation;
+ aData->mOldVisibleBounds = aLayer->GetValidRegion().GetBounds();
+
+ // When the visible region of aLayer changes (e.g. due to scrolling),
+ // three distinct types of invalidations need to be triggered:
+ // (1) Items (or parts of items) that have left the visible region need
+ // to be invalidated so that the pixels they painted are no longer
+ // part of the layer's valid region.
+ // (2) Items (or parts of items) that weren't in the old visible region
+ // but are in the new visible region need to be invalidated. This
+ // invalidation isn't required for painting the right layer
+ // contents, because these items weren't part of the layer's valid
+ // region, so they'd be painted anyway. It is, however, necessary in
+ // order to get an accurate invalid region for the layer tree that
+ // aLayer is in, for example for partial compositing.
+ // (3) Any changes that happened in the intersection of the old and the
+ // new visible region need to be invalidated. There shouldn't be any
+ // of these when scrolling static content.
+ //
+ // We'd like to guarantee that we won't invalidate anything in the
+ // intersection area of the old and the new visible region if all
+ // invalidation are of type (1) and (2). However, if we just call
+ // aLayer->InvalidateRegion for the invalidations of type (1) and (2),
+ // at some point we'll hit the complexity limit of the layer's invalid
+ // region. And the resulting region simplification can cause the region
+ // to intersect with the intersection of the old and the new visible
+ // region.
+ // In order to get around this problem, we're using the following approach:
+ // - aData->mIgnoreInvalidationsOutsideRect is set to a conservative
+ // approximation of the intersection of the old and the new visible
+ // region. At this point we don't know the layer's new visible region.
+ // - As long as we don't know the layer's new visible region, we ignore all
+ // invalidations outside that rectangle, so roughly some of the
+ // invalidations of type (1) and (2).
+ // - Once we know the layer's new visible region, which happens at some
+ // point during PostprocessRetainedLayers, we invalidate a conservative
+ // approximation of (1) and (2). Specifically, we invalidate the region
+ // union of the old visible bounds and the new visible bounds, minus
+ // aData->mIgnoreInvalidationsOutsideRect. That region is simple enough
+ // that it will never be simplified on its own.
+ // We unset mIgnoreInvalidationsOutsideRect at this point.
+ // - Any other invalidations that happen on the layer after this point, e.g.
+ // during WillEndTransaction, will just happen regularly. If they are of
+ // type (1) or (2), they won't change the layer's invalid region because
+ // they fall inside the region we invalidated in the previous step.
+ // Consequently, aData->mIgnoreInvalidationsOutsideRect is safe from
+ // invalidations as long as there are no invalidations of type (3).
+ aData->mIgnoreInvalidationsOutsideRect =
+ Some(maxNewVisibleBounds.Intersect(aData->mOldVisibleBounds));
+}
+
void
ContainerState::PreparePaintedLayerForUse(PaintedLayer* aLayer,
PaintedDisplayItemLayerUserData* aData,
const nsIFrame* aAnimatedGeometryRoot,
const nsIFrame* aReferenceFrame,
const nsPoint& aTopLeft,
bool didResetScrollPositionForLayerPixelAlignment)
{
@@ -2115,16 +2214,18 @@ ContainerState::PreparePaintedLayerForUs
// is close to aData->mAnimatedGeometryRootPosition if possible.
nsIntPoint pixOffset(RoundToMatchResidual(scaledOffset.x, aData->mAnimatedGeometryRootPosition.x),
RoundToMatchResidual(scaledOffset.y, aData->mAnimatedGeometryRootPosition.y));
aData->mTranslation = pixOffset;
pixOffset += mParameters.mOffset;
Matrix matrix = Matrix::Translation(pixOffset.x, pixOffset.y);
aLayer->SetBaseTransform(Matrix4x4::From2D(matrix));
+ ComputeAndSetIgnoreInvalidationRect(aLayer, aData, aAnimatedGeometryRoot, mBuilder, pixOffset);
+
// FIXME: Temporary workaround for bug 681192 and bug 724786.
#ifndef MOZ_WIDGET_ANDROID
// Calculate exact position of the top-left of the active scrolled root.
// This might not be 0,0 due to the snapping in ScaleToNearestPixels.
gfxPoint animatedGeometryRootTopLeft = scaledOffset - ThebesPoint(matrix.GetTranslation()) + mParameters.mOffset;
// If it has changed, then we need to invalidate the entire layer since the
// pixels in the layer buffer have the content at a (subpixel) offset
// from what we need.
@@ -2785,16 +2886,48 @@ static int32_t FindIndexOfLayerIn(nsTArr
if (aArray[i].mLayer == aLayer) {
return i;
}
}
return -1;
}
#endif
+static void
+InvalidateVisibleBoundsChangesForScrolledLayer(PaintedLayer* aLayer)
+{
+ PaintedDisplayItemLayerUserData* data =
+ static_cast<PaintedDisplayItemLayerUserData*>(aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
+
+ if (data->mIgnoreInvalidationsOutsideRect) {
+ // We haven't invalidated anything outside *data->mIgnoreInvalidationsOutsideRect
+ // during DLBI. Now is the right time to do that, because at this point aLayer
+ // knows its new visible region.
+ // We use the visible regions' bounds here (as opposed to the true region)
+ // in order to limit rgn's complexity. The only possible disadvantage of
+ // this is that it might cause us to unnecessarily recomposite parts of the
+ // window that are in the visible region's bounds but not in the visible
+ // region itself, but that is acceptable for scrolled layers.
+ nsIntRegion rgn;
+ rgn.Or(data->mOldVisibleBounds, aLayer->GetVisibleRegion().GetBounds());
+ rgn.Sub(rgn, *data->mIgnoreInvalidationsOutsideRect);
+ if (!rgn.IsEmpty()) {
+ aLayer->InvalidateRegion(rgn);
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Invalidating changes of the visible region bounds of the scrolled contents\n");
+ nsAutoCString str;
+ AppendToString(str, rgn);
+ printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get());
+ }
+#endif
+ }
+ data->mIgnoreInvalidationsOutsideRect = Nothing();
+ }
+}
template<typename FindOpaqueBackgroundColorCallbackType>
void ContainerState::FinishPaintedLayerData(PaintedLayerData& aData, FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor)
{
PaintedLayerData* data = &aData;
if (!data->mLayer) {
// No layer was recycled, so we create a new one.
@@ -3888,17 +4021,18 @@ FrameLayerBuilder::ComputeGeometryChange
#endif
}
if (!combined.IsEmpty()) {
if (notifyRenderingChanged) {
item->NotifyRenderingChanged();
}
InvalidatePostTransformRegion(paintedLayer,
combined.ScaleToOutsidePixels(layerData->mXScale, layerData->mYScale, layerData->mAppUnitsPerDevPixel),
- layerData->mTranslation);
+ layerData->mTranslation,
+ layerData);
}
aData->EndUpdate(geometry);
}
void
FrameLayerBuilder::AddPaintedDisplayItem(PaintedLayerData* aLayerData,
nsDisplayItem* aItem,
@@ -4006,17 +4140,18 @@ FrameLayerBuilder::AddPaintedDisplayItem
#endif
invalid.ScaleRoundOut(paintedData->mXScale, paintedData->mYScale);
if (hasClip) {
invalid.And(invalid, intClip);
}
InvalidatePostTransformRegion(layer, invalid,
- GetTranslationForPaintedLayer(layer));
+ GetTranslationForPaintedLayer(layer),
+ paintedData);
}
}
ClippedDisplayItem* cdi =
entry->mItems.AppendElement(ClippedDisplayItem(aItem,
mContainerLayerGeneration));
cdi->mInactiveLayerManager = tempManager;
}
}
@@ -4266,16 +4401,21 @@ ContainerState::PostprocessRetainedLayer
} else if (data) {
e->mVisibleRegion.Sub(e->mVisibleRegion, data->mOpaqueRegion);
}
}
SetOuterVisibleRegionForLayer(e->mLayer, e->mVisibleRegion,
e->mLayerContentsVisibleRect.width >= 0 ? &e->mLayerContentsVisibleRect : nullptr);
+ PaintedLayer* p = e->mLayer->AsPaintedLayer();
+ if (p) {
+ InvalidateVisibleBoundsChangesForScrolledLayer(p);
+ }
+
if (!e->mOpaqueRegion.IsEmpty()) {
const nsIFrame* animatedGeometryRootToCover = animatedGeometryRootForOpaqueness;
if (e->mOpaqueForAnimatedGeometryRootParent &&
nsLayoutUtils::GetAnimatedGeometryRootForFrame(mBuilder, e->mAnimatedGeometryRoot->GetParent(),
mContainerAnimatedGeometryRoot)
== mContainerAnimatedGeometryRoot) {
animatedGeometryRootToCover = mContainerAnimatedGeometryRoot;
data = FindOpaqueRegionEntry(opaqueRegions,