From ce6e47942946e1fb68e026f98104dfe0a78878ea Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Mon, 24 Jun 2013 13:34:33 +1200 Subject: [PATCH] Bug 876542. Rework TransformFixedLayers so that it explicitly makes layer anchor points invariant under a change in transform of a specified ancestor layer. r=Cwiiis,kats --- gfx/layers/Layers.cpp | 2 +- .../composite/AsyncCompositionManager.cpp | 182 +++++++++--------- .../composite/AsyncCompositionManager.h | 17 +- 3 files changed, 103 insertions(+), 98 deletions(-) diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index b0716e80ecc..e1dfe6d0e68 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -1142,7 +1142,7 @@ Layer::PrintInfo(nsACString& aTo, const char* aPrefix) aTo += " [componentAlpha]"; } if (GetIsFixedPosition()) { - aTo += " [isFixedPosition]"; + aTo.AppendPrintf(" [isFixedPosition anchor=%f,%f]", mAnchor.x, mAnchor.y); } return aTo; diff --git a/gfx/layers/composite/AsyncCompositionManager.cpp b/gfx/layers/composite/AsyncCompositionManager.cpp index 6599f0ddc89..215748064e7 100644 --- a/gfx/layers/composite/AsyncCompositionManager.cpp +++ b/gfx/layers/composite/AsyncCompositionManager.cpp @@ -109,77 +109,92 @@ AsyncCompositionManager::ComputeRotation() } } -// Do a breadth-first search to find the first layer in the tree that is -// scrollable. -static void -Translate2D(gfx3DMatrix& aTransform, const gfxPoint& aOffset) +static bool +GetBaseTransform2D(Layer* aLayer, gfxMatrix* aTransform) { - aTransform._41 += aOffset.x; - aTransform._42 += aOffset.y; + // Start with the animated transform if there is one + return (aLayer->AsLayerComposite()->GetShadowTransformSetByAnimation() ? + aLayer->GetLocalTransform() : aLayer->GetTransform()).Is2D(aTransform); } void -AsyncCompositionManager::TransformFixedLayers(Layer* aLayer, - const gfxPoint& aTranslation, - const gfxSize& aScaleDiff, - const gfx::Margin& aFixedLayerMargins) +AsyncCompositionManager::AlignFixedLayersForAnchorPoint(Layer* aLayer, + Layer* aTransformedSubtreeRoot, + const gfx3DMatrix& aPreviousTransformForRoot) { - if (aLayer->GetIsFixedPosition() && + if (aLayer != aTransformedSubtreeRoot && aLayer->GetIsFixedPosition() && !aLayer->GetParent()->GetIsFixedPosition()) { - // When a scale has been applied to a layer, it focuses around (0,0). - // The anchor position is used here as a scale focus point (assuming that - // aScaleDiff has already been applied) to re-focus the scale. - const gfxPoint& anchor = aLayer->GetFixedPositionAnchor(); - gfxPoint translation(aTranslation - (anchor - anchor / aScaleDiff)); + // Insert a translation so that the position of the anchor point is the same + // before and after the change to the transform of aTransformedSubtreeRoot. + // This currently only works for fixed layers with 2D transforms. - // Offset this translation by the fixed layer margins, depending on what - // side of the viewport the layer is anchored to, reconciling the - // difference between the current fixed layer margins and the Gecko-side - // fixed layer margins. - // aFixedLayerMargins are the margins we expect to be at at the current - // time, obtained via SyncViewportInfo, and fixedMargins are the margins - // that were used during layout. - // If top/left of fixedMargins are negative, that indicates that this layer - // represents auto-positioned elements, and should not be affected by - // fixed margins at all. - const gfx::Margin& fixedMargins = aLayer->GetFixedPositionMargins(); - if (fixedMargins.left >= 0) { - if (anchor.x > 0) { - translation.x -= aFixedLayerMargins.right - fixedMargins.right; - } else { - translation.x += aFixedLayerMargins.left - fixedMargins.left; + // Accumulate the transforms between this layer and the subtree root layer. + gfxMatrix ancestorTransform; + for (Layer* l = aLayer->GetParent(); l != aTransformedSubtreeRoot; + l = l->GetParent()) { + gfxMatrix l2D; + if (!GetBaseTransform2D(l, &l2D)) { + return; } + ancestorTransform.Multiply(l2D); } - if (fixedMargins.top >= 0) { - if (anchor.y > 0) { - translation.y -= aFixedLayerMargins.bottom - fixedMargins.bottom; - } else { - translation.y += aFixedLayerMargins.top - fixedMargins.top; - } + gfxMatrix oldRootTransform; + gfxMatrix newRootTransform; + if (!aPreviousTransformForRoot.Is2D(&oldRootTransform) || + !aTransformedSubtreeRoot->GetLocalTransform().Is2D(&newRootTransform)) { + return; } + // Calculate the cumulative transforms between the subtree root with the + // old transform and the current transform. + gfxMatrix oldCumulativeTransform = ancestorTransform * oldRootTransform; + gfxMatrix newCumulativeTransform = ancestorTransform * newRootTransform; + if (newCumulativeTransform.IsSingular()) { + return; + } + gfxMatrix newCumulativeTransformInverse = newCumulativeTransform; + newCumulativeTransformInverse.Invert(); + + // Now work out the translation necessary to make sure the layer doesn't + // move given the new sub-tree root transform. + // Transforming the locallyTransformedAnchor by oldCumulativeTransform + // returns the layer's anchor point relative to the parent of + // aTransformedSubtreeRoot, before the new transform was applied. + // Then, applying newCumulativeTransformInverse maps that point relative + // to the layer's parent, which is the same coordinate space as + // locallyTransformedAnchor again, allowing us to subtract them and find + // out the offset necessary to make sure the layer stays stationary. + gfxMatrix layerTransform; + if (!GetBaseTransform2D(aLayer, &layerTransform)) { + return; + } + gfxPoint locallyTransformedAnchor = + layerTransform.Transform(aLayer->GetFixedPositionAnchor()); + gfxPoint oldAnchorPositionInNewSpace = + newCumulativeTransformInverse.Transform( + oldCumulativeTransform.Transform(locallyTransformedAnchor)); + gfxPoint translation = oldAnchorPositionInNewSpace - locallyTransformedAnchor; + + // Finally, apply the 2D translation to the layer transform. + layerTransform.x0 += translation.x; + layerTransform.y0 += translation.y; + // The transform already takes the resolution scale into account. Since we // will apply the resolution scale again when computing the effective // transform, we must apply the inverse resolution scale here. - LayerComposite* layerComposite = aLayer->AsLayerComposite(); - gfx3DMatrix layerTransform; - if (layerComposite->GetShadowTransformSetByAnimation()) { - // Start with the animated transform - layerTransform = aLayer->GetLocalTransform(); - } else { - layerTransform = aLayer->GetTransform(); - } - Translate2D(layerTransform, translation); + gfx3DMatrix layerTransform3D = gfx3DMatrix::From2D(layerTransform); if (ContainerLayer* c = aLayer->AsContainerLayer()) { - layerTransform.Scale(1.0f/c->GetPreXScale(), - 1.0f/c->GetPreYScale(), - 1); - } - layerTransform.ScalePost(1.0f/aLayer->GetPostXScale(), - 1.0f/aLayer->GetPostYScale(), + layerTransform3D.Scale(1.0f/c->GetPreXScale(), + 1.0f/c->GetPreYScale(), 1); - layerComposite->SetShadowTransform(layerTransform); + } + layerTransform3D.ScalePost(1.0f/aLayer->GetPostXScale(), + 1.0f/aLayer->GetPostYScale(), + 1); + + LayerComposite* layerComposite = aLayer->AsLayerComposite(); + layerComposite->SetShadowTransform(layerTransform3D); layerComposite->SetShadowTransformSetByAnimation(false); const nsIntRect* clipRect = aLayer->GetClipRect(); @@ -196,7 +211,7 @@ AsyncCompositionManager::TransformFixedLayers(Layer* aLayer, for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { - TransformFixedLayers(child, aTranslation, aScaleDiff, aFixedLayerMargins); + AlignFixedLayersForAnchorPoint(child, aTransformedSubtreeRoot, aPreviousTransformForRoot); } } @@ -342,6 +357,7 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(TimeStamp aCurrentFram if (AsyncPanZoomController* controller = container->GetAsyncPanZoomController()) { LayerComposite* layerComposite = aLayer->AsLayerComposite(); + gfx3DMatrix oldTransform = aLayer->GetTransform(); ViewTransform treeTransform; ScreenPoint scrollOffset; @@ -387,11 +403,20 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(TimeStamp aCurrentFram NS_ASSERTION(!layerComposite->GetShadowTransformSetByAnimation(), "overwriting animated transform!"); - TransformFixedLayers( - aLayer, - gfxPoint(-treeTransform.mTranslation.x, -treeTransform.mTranslation.y), - gfxSize(treeTransform.mScale.scale, treeTransform.mScale.scale), - fixedLayerMargins); + // Apply resolution scaling to the old transform - the layer tree as it is + // doesn't have the necessary transform to display correctly. +#ifdef MOZ_WIDGET_ANDROID + // XXX We use rootTransform instead of the resolution on the individual layer's + // FrameMetrics on Fennec because the resolution is set on the root layer rather + // than the scrollable layer. See bug 732971. On non-Fennec we do the right thing. + LayoutDeviceToLayerScale resolution(1.0 / rootTransform.GetXScale(), + 1.0 / rootTransform.GetYScale()); +#else + LayoutDeviceToLayerScale resolution = metrics.mResolution; +#endif + oldTransform.Scale(resolution.scale, resolution.scale, 1); + + AlignFixedLayersForAnchorPoint(aLayer, aLayer, oldTransform); appliedTransform = true; } @@ -409,6 +434,7 @@ AsyncCompositionManager::TransformScrollableLayer(Layer* aLayer, const LayoutDev // We must apply the resolution scale before a pan/zoom transform, so we call // GetTransform here. const gfx3DMatrix& currentTransform = aLayer->GetTransform(); + gfx3DMatrix oldTransform = currentTransform; gfx3DMatrix treeTransform; @@ -471,33 +497,6 @@ AsyncCompositionManager::TransformScrollableLayer(Layer* aLayer, const LayoutDev LayerPoint translation = (userScroll / zoomAdjust) - geckoScroll; treeTransform = gfx3DMatrix(ViewTransform(-translation, userZoom / metrics.mDevPixelsPerCSSPixel)); - // Translate fixed position layers so that they stay in the correct position - // when userScroll and geckoScroll differ. - gfxPoint fixedOffset; - gfxSize scaleDiff; - - LayerRect content = mContentRect * geckoZoom; - // If the contents can fit entirely within the widget area on a particular - // dimension, we need to translate and scale so that the fixed layers remain - // within the page boundaries. - if (mContentRect.width * userZoom.scale < metrics.mCompositionBounds.width) { - fixedOffset.x = -geckoScroll.x; - scaleDiff.width = std::min(1.0f, metrics.mCompositionBounds.width / content.width); - } else { - fixedOffset.x = clamped(userScroll.x / zoomAdjust.scale, content.x, - content.XMost() - metrics.mCompositionBounds.width / zoomAdjust.scale) - geckoScroll.x; - scaleDiff.width = zoomAdjust.scale; - } - - if (mContentRect.height * userZoom.scale < metrics.mCompositionBounds.height) { - fixedOffset.y = -geckoScroll.y; - scaleDiff.height = std::min(1.0f, metrics.mCompositionBounds.height / content.height); - } else { - fixedOffset.y = clamped(userScroll.y / zoomAdjust.scale, content.y, - content.YMost() - metrics.mCompositionBounds.height / zoomAdjust.scale) - geckoScroll.y; - scaleDiff.height = zoomAdjust.scale; - } - // The transform already takes the resolution scale into account. Since we // will apply the resolution scale again when computing the effective // transform, we must apply the inverse resolution scale here. @@ -511,7 +510,14 @@ AsyncCompositionManager::TransformScrollableLayer(Layer* aLayer, const LayoutDev layerComposite->SetShadowTransform(computedTransform); NS_ASSERTION(!layerComposite->GetShadowTransformSetByAnimation(), "overwriting animated transform!"); - TransformFixedLayers(aLayer, fixedOffset, scaleDiff, fixedLayerMargins); + + // Apply resolution scaling to the old transform - the layer tree as it is + // doesn't have the necessary transform to display correctly. + oldTransform.Scale(aResolution.scale, aResolution.scale, 1); + + // Make sure fixed position layers don't move away from their anchor points + // when we're asynchronously panning or zooming + AlignFixedLayersForAnchorPoint(aLayer, aLayer, oldTransform); } bool diff --git a/gfx/layers/composite/AsyncCompositionManager.h b/gfx/layers/composite/AsyncCompositionManager.h index 5413d95d860..394c8deba8d 100644 --- a/gfx/layers/composite/AsyncCompositionManager.h +++ b/gfx/layers/composite/AsyncCompositionManager.h @@ -144,16 +144,15 @@ private: ScreenPoint& aOffset); /** - * Recursively applies the given translation to all top-level fixed position - * layers that are descendants of the given layer. - * aScaleDiff is considered to be the scale transformation applied when - * displaying the layers, and is used to make sure the anchor points of - * fixed position layers remain in the same position. + * Adds a translation to the transform of any fixed-pos layer descendant of + * aTransformedSubtreeRoot whose parent layer is not fixed. The translation is + * chosen so that the layer's anchor point relative to aTransformedSubtreeRoot's + * parent layer is the same as it was when aTransformedSubtreeRoot's + * GetLocalTransform() was aPreviousTransformForRoot. */ - void TransformFixedLayers(Layer* aLayer, - const gfxPoint& aTranslation, - const gfxSize& aScaleDiff, - const gfx::Margin& aFixedLayerMargins); + void AlignFixedLayersForAnchorPoint(Layer* aLayer, + Layer* aTransformedSubtreeRoot, + const gfx3DMatrix& aPreviousTransformForRoot); /** * DRAWING PHASE ONLY