diff -r 000000000000 -r 6474c204b198 gfx/layers/composite/AsyncCompositionManager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gfx/layers/composite/AsyncCompositionManager.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,948 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=2 et tw=80 : */ +/* 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 "mozilla/layers/AsyncCompositionManager.h" +#include // for uint32_t +#include "AnimationCommon.h" // for ComputedTimingFunction +#include "CompositorParent.h" // for CompositorParent, etc +#include "FrameMetrics.h" // for FrameMetrics +#include "LayerManagerComposite.h" // for LayerManagerComposite, etc +#include "Layers.h" // for Layer, ContainerLayer, etc +#include "gfxPoint.h" // for gfxPoint, gfxSize +#include "gfxPoint3D.h" // for gfxPoint3D +#include "mozilla/WidgetUtils.h" // for ComputeTransformForRotation +#include "mozilla/gfx/BaseRect.h" // for BaseRect +#include "mozilla/gfx/Point.h" // for RoundedToInt, PointTyped +#include "mozilla/gfx/Rect.h" // for RoundedToInt, RectTyped +#include "mozilla/gfx/ScaleFactor.h" // for ScaleFactor +#include "mozilla/layers/AsyncPanZoomController.h" +#include "mozilla/layers/Compositor.h" // for Compositor +#include "nsAnimationManager.h" // for ElementAnimations +#include "nsCSSPropList.h" +#include "nsCoord.h" // for NSAppUnitsToFloatPixels, etc +#include "nsDebug.h" // for NS_ASSERTION, etc +#include "nsDeviceContext.h" // for nsDeviceContext +#include "nsDisplayList.h" // for nsDisplayTransform, etc +#include "nsMathUtils.h" // for NS_round +#include "nsPoint.h" // for nsPoint +#include "nsRect.h" // for nsIntRect +#include "nsRegion.h" // for nsIntRegion +#include "nsStyleAnimation.h" // for nsStyleAnimation::Value, etc +#include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc +#include "nsTArrayForwardDeclare.h" // for InfallibleTArray +#if defined(MOZ_WIDGET_ANDROID) +# include +# include "AndroidBridge.h" +#endif +#include "GeckoProfiler.h" + +struct nsCSSValueSharedList; + +using namespace mozilla::dom; +using namespace mozilla::gfx; + +namespace mozilla { +namespace layers { + +enum Op { Resolve, Detach }; + +static bool +IsSameDimension(ScreenOrientation o1, ScreenOrientation o2) +{ + bool isO1portrait = (o1 == eScreenOrientation_PortraitPrimary || o1 == eScreenOrientation_PortraitSecondary); + bool isO2portrait = (o2 == eScreenOrientation_PortraitPrimary || o2 == eScreenOrientation_PortraitSecondary); + return !(isO1portrait ^ isO2portrait); +} + +static bool +ContentMightReflowOnOrientationChange(const nsIntRect& rect) +{ + return rect.width != rect.height; +} + +template +static void +WalkTheTree(Layer* aLayer, + bool& aReady, + const TargetConfig& aTargetConfig) +{ + if (RefLayer* ref = aLayer->AsRefLayer()) { + if (const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(ref->GetReferentId())) { + if (Layer* referent = state->mRoot) { + if (!ref->GetVisibleRegion().IsEmpty()) { + ScreenOrientation chromeOrientation = aTargetConfig.orientation(); + ScreenOrientation contentOrientation = state->mTargetConfig.orientation(); + if (!IsSameDimension(chromeOrientation, contentOrientation) && + ContentMightReflowOnOrientationChange(aTargetConfig.clientBounds())) { + aReady = false; + } + } + + if (OP == Resolve) { + ref->ConnectReferentLayer(referent); + } else { + ref->DetachReferentLayer(referent); + } + } + } + } + for (Layer* child = aLayer->GetFirstChild(); + child; child = child->GetNextSibling()) { + WalkTheTree(child, aReady, aTargetConfig); + } +} + +void +AsyncCompositionManager::ResolveRefLayers() +{ + if (!mLayerManager->GetRoot()) { + return; + } + + mReadyForCompose = true; + WalkTheTree(mLayerManager->GetRoot(), + mReadyForCompose, + mTargetConfig); +} + +void +AsyncCompositionManager::DetachRefLayers() +{ + if (!mLayerManager->GetRoot()) { + return; + } + WalkTheTree(mLayerManager->GetRoot(), + mReadyForCompose, + mTargetConfig); +} + +void +AsyncCompositionManager::ComputeRotation() +{ + if (!mTargetConfig.naturalBounds().IsEmpty()) { + mLayerManager->SetWorldTransform( + ComputeTransformForRotation(mTargetConfig.naturalBounds(), + mTargetConfig.rotation())); + } +} + +static bool +GetBaseTransform2D(Layer* aLayer, Matrix* aTransform) +{ + // Start with the animated transform if there is one + return (aLayer->AsLayerComposite()->GetShadowTransformSetByAnimation() ? + aLayer->GetLocalTransform() : aLayer->GetTransform()).Is2D(aTransform); +} + +static void +TranslateShadowLayer2D(Layer* aLayer, + const gfxPoint& aTranslation) +{ + Matrix layerTransform; + if (!GetBaseTransform2D(aLayer, &layerTransform)) { + return; + } + + // Apply the 2D translation to the layer transform. + layerTransform._31 += aTranslation.x; + layerTransform._32 += aTranslation.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. + Matrix4x4 layerTransform3D = Matrix4x4::From2D(layerTransform); + if (ContainerLayer* c = aLayer->AsContainerLayer()) { + layerTransform3D.Scale(1.0f/c->GetPreXScale(), + 1.0f/c->GetPreYScale(), + 1); + } + layerTransform3D = layerTransform3D * + Matrix4x4().Scale(1.0f/aLayer->GetPostXScale(), + 1.0f/aLayer->GetPostYScale(), + 1); + + LayerComposite* layerComposite = aLayer->AsLayerComposite(); + layerComposite->SetShadowTransform(layerTransform3D); + layerComposite->SetShadowTransformSetByAnimation(false); + + const nsIntRect* clipRect = aLayer->GetClipRect(); + if (clipRect) { + nsIntRect transformedClipRect(*clipRect); + transformedClipRect.MoveBy(aTranslation.x, aTranslation.y); + layerComposite->SetShadowClipRect(&transformedClipRect); + } +} + +static bool +AccumulateLayerTransforms2D(Layer* aLayer, + Layer* aAncestor, + Matrix& aMatrix) +{ + // Accumulate the transforms between this layer and the subtree root layer. + for (Layer* l = aLayer; l && l != aAncestor; l = l->GetParent()) { + Matrix l2D; + if (!GetBaseTransform2D(l, &l2D)) { + return false; + } + aMatrix *= l2D; + } + + return true; +} + +static LayerPoint +GetLayerFixedMarginsOffset(Layer* aLayer, + const LayerMargin& aFixedLayerMargins) +{ + // Work out the necessary translation, in root scrollable layer space. + // Because fixed layer margins are stored relative to the root scrollable + // layer, we can just take the difference between these values. + LayerPoint translation; + const LayerPoint& anchor = aLayer->GetFixedPositionAnchor(); + const LayerMargin& fixedMargins = aLayer->GetFixedPositionMargins(); + + if (fixedMargins.left >= 0) { + if (anchor.x > 0) { + translation.x -= aFixedLayerMargins.right - fixedMargins.right; + } else { + translation.x += aFixedLayerMargins.left - fixedMargins.left; + } + } + + if (fixedMargins.top >= 0) { + if (anchor.y > 0) { + translation.y -= aFixedLayerMargins.bottom - fixedMargins.bottom; + } else { + translation.y += aFixedLayerMargins.top - fixedMargins.top; + } + } + + return translation; +} + +static gfxFloat +IntervalOverlap(gfxFloat aTranslation, gfxFloat aMin, gfxFloat aMax) +{ + // Determine the amount of overlap between the 1D vector |aTranslation| + // and the interval [aMin, aMax]. + if (aTranslation > 0) { + return std::max(0.0, std::min(aMax, aTranslation) - std::max(aMin, 0.0)); + } else { + return std::min(0.0, std::max(aMin, aTranslation) - std::min(aMax, 0.0)); + } +} + +void +AsyncCompositionManager::AlignFixedAndStickyLayers(Layer* aLayer, + Layer* aTransformedSubtreeRoot, + const Matrix4x4& aPreviousTransformForRoot, + const LayerMargin& aFixedLayerMargins) +{ + bool isRootFixed = aLayer->GetIsFixedPosition() && + !aLayer->GetParent()->GetIsFixedPosition(); + bool isStickyForSubtree = aLayer->GetIsStickyPosition() && + aTransformedSubtreeRoot->AsContainerLayer() && + aLayer->GetStickyScrollContainerId() == + aTransformedSubtreeRoot->AsContainerLayer()->GetFrameMetrics().GetScrollId(); + if (aLayer != aTransformedSubtreeRoot && (isRootFixed || isStickyForSubtree)) { + // 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. + + // Accumulate the transforms between this layer and the subtree root layer. + Matrix ancestorTransform; + if (!AccumulateLayerTransforms2D(aLayer->GetParent(), aTransformedSubtreeRoot, + ancestorTransform)) { + return; + } + + Matrix oldRootTransform; + Matrix 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. + Matrix oldCumulativeTransform = ancestorTransform * oldRootTransform; + Matrix newCumulativeTransform = ancestorTransform * newRootTransform; + if (newCumulativeTransform.IsSingular()) { + return; + } + Matrix 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. + Matrix layerTransform; + if (!GetBaseTransform2D(aLayer, &layerTransform)) { + return; + } + + // Calculate any offset necessary, in previous transform sub-tree root + // space. This is used to make sure fixed position content respects + // content document fixed position margins. + LayerPoint offsetInOldSubtreeLayerSpace = GetLayerFixedMarginsOffset(aLayer, aFixedLayerMargins); + + // Add the above offset to the anchor point so we can offset the layer by + // and amount that's specified in old subtree layer space. + const LayerPoint& anchorInOldSubtreeLayerSpace = aLayer->GetFixedPositionAnchor(); + LayerPoint offsetAnchorInOldSubtreeLayerSpace = anchorInOldSubtreeLayerSpace + offsetInOldSubtreeLayerSpace; + + // Add the local layer transform to the two points to make the equation + // below this section more convenient. + Point anchor(anchorInOldSubtreeLayerSpace.x, anchorInOldSubtreeLayerSpace.y); + Point offsetAnchor(offsetAnchorInOldSubtreeLayerSpace.x, offsetAnchorInOldSubtreeLayerSpace.y); + Point locallyTransformedAnchor = layerTransform * anchor; + Point locallyTransformedOffsetAnchor = layerTransform * offsetAnchor; + + // 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. + Point oldAnchorPositionInNewSpace = + newCumulativeTransformInverse * (oldCumulativeTransform * locallyTransformedOffsetAnchor); + Point translation = oldAnchorPositionInNewSpace - locallyTransformedAnchor; + + if (aLayer->GetIsStickyPosition()) { + // For sticky positioned layers, the difference between the two rectangles + // defines a pair of translation intervals in each dimension through which + // the layer should not move relative to the scroll container. To + // accomplish this, we limit each dimension of the |translation| to that + // part of it which overlaps those intervals. + const LayerRect& stickyOuter = aLayer->GetStickyScrollRangeOuter(); + const LayerRect& stickyInner = aLayer->GetStickyScrollRangeInner(); + + translation.y = IntervalOverlap(translation.y, stickyOuter.y, stickyOuter.YMost()) - + IntervalOverlap(translation.y, stickyInner.y, stickyInner.YMost()); + translation.x = IntervalOverlap(translation.x, stickyOuter.x, stickyOuter.XMost()) - + IntervalOverlap(translation.x, stickyInner.x, stickyInner.XMost()); + } + + // Finally, apply the 2D translation to the layer transform. + TranslateShadowLayer2D(aLayer, ThebesPoint(translation)); + + // The transform has now been applied, so there's no need to iterate over + // child layers. + return; + } + + // Fixed layers are relative to their nearest scrollable layer, so when we + // encounter a scrollable layer, reset the transform to that layer and remove + // the fixed margins. + if (aLayer->AsContainerLayer() && + aLayer->AsContainerLayer()->GetFrameMetrics().IsScrollable() && + aLayer != aTransformedSubtreeRoot) { + AlignFixedAndStickyLayers(aLayer, aLayer, aLayer->GetTransform(), LayerMargin(0, 0, 0, 0)); + return; + } + + for (Layer* child = aLayer->GetFirstChild(); + child; child = child->GetNextSibling()) { + AlignFixedAndStickyLayers(child, aTransformedSubtreeRoot, + aPreviousTransformForRoot, aFixedLayerMargins); + } +} + +static void +SampleValue(float aPortion, Animation& aAnimation, nsStyleAnimation::Value& aStart, + nsStyleAnimation::Value& aEnd, Animatable* aValue) +{ + nsStyleAnimation::Value interpolatedValue; + NS_ASSERTION(aStart.GetUnit() == aEnd.GetUnit() || + aStart.GetUnit() == nsStyleAnimation::eUnit_None || + aEnd.GetUnit() == nsStyleAnimation::eUnit_None, "Must have same unit"); + nsStyleAnimation::Interpolate(aAnimation.property(), aStart, aEnd, + aPortion, interpolatedValue); + if (aAnimation.property() == eCSSProperty_opacity) { + *aValue = interpolatedValue.GetFloatValue(); + return; + } + + nsCSSValueSharedList* interpolatedList = + interpolatedValue.GetCSSValueSharedListValue(); + + TransformData& data = aAnimation.data().get_TransformData(); + nsPoint origin = data.origin(); + // we expect all our transform data to arrive in css pixels, so here we must + // adjust to dev pixels. + double cssPerDev = double(nsDeviceContext::AppUnitsPerCSSPixel()) + / double(data.appUnitsPerDevPixel()); + gfxPoint3D transformOrigin = data.transformOrigin(); + transformOrigin.x = transformOrigin.x * cssPerDev; + transformOrigin.y = transformOrigin.y * cssPerDev; + gfxPoint3D perspectiveOrigin = data.perspectiveOrigin(); + perspectiveOrigin.x = perspectiveOrigin.x * cssPerDev; + perspectiveOrigin.y = perspectiveOrigin.y * cssPerDev; + nsDisplayTransform::FrameTransformProperties props(interpolatedList, + transformOrigin, + perspectiveOrigin, + data.perspective()); + gfx3DMatrix transform = + nsDisplayTransform::GetResultingTransformMatrix(props, origin, + data.appUnitsPerDevPixel(), + &data.bounds()); + gfxPoint3D scaledOrigin = + gfxPoint3D(NS_round(NSAppUnitsToFloatPixels(origin.x, data.appUnitsPerDevPixel())), + NS_round(NSAppUnitsToFloatPixels(origin.y, data.appUnitsPerDevPixel())), + 0.0f); + + transform.Translate(scaledOrigin); + + InfallibleTArray functions; + Matrix4x4 realTransform; + ToMatrix4x4(transform, realTransform); + functions.AppendElement(TransformMatrix(realTransform)); + *aValue = functions; +} + +static bool +SampleAnimations(Layer* aLayer, TimeStamp aPoint) +{ + AnimationArray& animations = aLayer->GetAnimations(); + InfallibleTArray& animationData = aLayer->GetAnimationData(); + + bool activeAnimations = false; + + for (uint32_t i = animations.Length(); i-- !=0; ) { + Animation& animation = animations[i]; + AnimData& animData = animationData[i]; + + activeAnimations = true; + + TimeDuration elapsedDuration = aPoint - animation.startTime(); + // Skip animations that are yet to start. + // + // Currently, this should only happen when the refresh driver is under test + // control and is made to produce a time in the past or is restored from + // test control causing it to jump backwards in time. + // + // Since activeAnimations is true, this could mean we keep compositing + // unnecessarily during the delay, but so long as this only happens while + // the refresh driver is under test control that should be ok. + if (elapsedDuration.ToSeconds() < 0) { + continue; + } + + double numIterations = animation.numIterations() != -1 ? + animation.numIterations() : NS_IEEEPositiveInfinity(); + double positionInIteration = + ElementAnimations::GetPositionInIteration(elapsedDuration, + animation.duration(), + numIterations, + animation.direction()); + + NS_ABORT_IF_FALSE(0.0 <= positionInIteration && + positionInIteration <= 1.0, + "position should be in [0-1]"); + + int segmentIndex = 0; + AnimationSegment* segment = animation.segments().Elements(); + while (segment->endPortion() < positionInIteration) { + ++segment; + ++segmentIndex; + } + + double positionInSegment = (positionInIteration - segment->startPortion()) / + (segment->endPortion() - segment->startPortion()); + + double portion = animData.mFunctions[segmentIndex]->GetValue(positionInSegment); + + // interpolate the property + Animatable interpolatedValue; + SampleValue(portion, animation, animData.mStartValues[segmentIndex], + animData.mEndValues[segmentIndex], &interpolatedValue); + LayerComposite* layerComposite = aLayer->AsLayerComposite(); + switch (animation.property()) { + case eCSSProperty_opacity: + { + layerComposite->SetShadowOpacity(interpolatedValue.get_float()); + break; + } + case eCSSProperty_transform: + { + Matrix4x4 matrix = interpolatedValue.get_ArrayOfTransformFunction()[0].get_TransformMatrix().value(); + if (ContainerLayer* c = aLayer->AsContainerLayer()) { + matrix = matrix * Matrix4x4().Scale(c->GetInheritedXScale(), + c->GetInheritedYScale(), + 1); + } + layerComposite->SetShadowTransform(matrix); + layerComposite->SetShadowTransformSetByAnimation(true); + break; + } + default: + NS_WARNING("Unhandled animated property"); + } + } + + for (Layer* child = aLayer->GetFirstChild(); child; + child = child->GetNextSibling()) { + activeAnimations |= SampleAnimations(child, aPoint); + } + + return activeAnimations; +} + +bool +AsyncCompositionManager::ApplyAsyncContentTransformToTree(TimeStamp aCurrentFrame, + Layer *aLayer, + bool* aWantNextFrame) +{ + bool appliedTransform = false; + for (Layer* child = aLayer->GetFirstChild(); + child; child = child->GetNextSibling()) { + appliedTransform |= + ApplyAsyncContentTransformToTree(aCurrentFrame, child, aWantNextFrame); + } + + ContainerLayer* container = aLayer->AsContainerLayer(); + if (!container) { + return appliedTransform; + } + + if (AsyncPanZoomController* controller = container->GetAsyncPanZoomController()) { + LayerComposite* layerComposite = aLayer->AsLayerComposite(); + Matrix4x4 oldTransform = aLayer->GetTransform(); + + ViewTransform treeTransform; + ScreenPoint scrollOffset; + *aWantNextFrame |= + controller->SampleContentTransformForFrame(aCurrentFrame, + &treeTransform, + scrollOffset); + + const FrameMetrics& metrics = container->GetFrameMetrics(); + CSSToLayerScale paintScale = metrics.LayersPixelsPerCSSPixel(); + CSSRect displayPort(metrics.mCriticalDisplayPort.IsEmpty() ? + metrics.mDisplayPort : metrics.mCriticalDisplayPort); + LayerMargin fixedLayerMargins(0, 0, 0, 0); + ScreenPoint offset(0, 0); + SyncFrameMetrics(scrollOffset, treeTransform.mScale.scale, metrics.mScrollableRect, + mLayersUpdated, displayPort, paintScale, + mIsFirstPaint, fixedLayerMargins, offset); + + mIsFirstPaint = false; + mLayersUpdated = false; + + // Apply the render offset + mLayerManager->GetCompositor()->SetScreenRenderOffset(offset); + + Matrix4x4 transform; + ToMatrix4x4(gfx3DMatrix(treeTransform), transform); + transform = transform * aLayer->GetTransform(); + + // GetTransform already takes the pre- and post-scale into account. Since we + // will apply the pre- and post-scale again when computing the effective + // transform, we must apply the inverses here. + transform.Scale(1.0f/container->GetPreXScale(), + 1.0f/container->GetPreYScale(), + 1); + transform = transform * Matrix4x4().Scale(1.0f/aLayer->GetPostXScale(), + 1.0f/aLayer->GetPostYScale(), + 1); + layerComposite->SetShadowTransform(transform); + NS_ASSERTION(!layerComposite->GetShadowTransformSetByAnimation(), + "overwriting animated transform!"); + + // Apply resolution scaling to the old transform - the layer tree as it is + // doesn't have the necessary transform to display correctly. + LayoutDeviceToLayerScale resolution = metrics.mCumulativeResolution; + oldTransform.Scale(resolution.scale, resolution.scale, 1); + + AlignFixedAndStickyLayers(aLayer, aLayer, oldTransform, fixedLayerMargins); + + appliedTransform = true; + } + + if (container->GetScrollbarDirection() != Layer::NONE) { + ApplyAsyncTransformToScrollbar(aCurrentFrame, container); + } + return appliedTransform; +} + +static bool +LayerHasNonContainerDescendants(ContainerLayer* aContainer) +{ + for (Layer* child = aContainer->GetFirstChild(); + child; child = child->GetNextSibling()) { + ContainerLayer* container = child->AsContainerLayer(); + if (!container || LayerHasNonContainerDescendants(container)) { + return true; + } + } + + return false; +} + +static bool +LayerIsContainerForScrollbarTarget(Layer* aTarget, ContainerLayer* aScrollbar) +{ + if (!aTarget->AsContainerLayer()) { + return false; + } + AsyncPanZoomController* apzc = aTarget->AsContainerLayer()->GetAsyncPanZoomController(); + if (!apzc) { + return false; + } + const FrameMetrics& metrics = aTarget->AsContainerLayer()->GetFrameMetrics(); + if (metrics.GetScrollId() != aScrollbar->GetScrollbarTargetContainerId()) { + return false; + } + return true; +} + +static void +ApplyAsyncTransformToScrollbarForContent(TimeStamp aCurrentFrame, ContainerLayer* aScrollbar, + Layer* aContent, bool aScrollbarIsChild) +{ + ContainerLayer* content = aContent->AsContainerLayer(); + if (!LayerHasNonContainerDescendants(content)) { + return; + } + + const FrameMetrics& metrics = content->GetFrameMetrics(); + AsyncPanZoomController* apzc = content->GetAsyncPanZoomController(); + + if (aScrollbarIsChild) { + // Because we try to apply the scrollbar transform before we apply the async transform on + // the actual content, we need to ensure that the APZC has updated any pending animations + // to the current frame timestamp before we extract the transforms from it. The code in this + // block accomplishes that and throws away the temp variables. + // TODO: it might be cleaner to do a pass through the layer tree to advance all the APZC + // transforms before updating the layer shadow transforms. That will allow removal of this code. + ViewTransform treeTransform; + ScreenPoint scrollOffset; + apzc->SampleContentTransformForFrame(aCurrentFrame, &treeTransform, scrollOffset); + } + + gfx3DMatrix asyncTransform = gfx3DMatrix(apzc->GetCurrentAsyncTransform()); + gfx3DMatrix nontransientTransform = apzc->GetNontransientAsyncTransform(); + gfx3DMatrix transientTransform = asyncTransform * nontransientTransform.Inverse(); + + // |transientTransform| represents the amount by which we have scrolled and + // zoomed since the last paint. Because the scrollbar was sized and positioned based + // on the painted content, we need to adjust it based on transientTransform so that + // it reflects what the user is actually seeing now. + // - The scroll thumb needs to be scaled in the direction of scrolling by the inverse + // of the transientTransform scale (representing the zoom). This is because zooming + // in decreases the fraction of the whole scrollable rect that is in view. + // - It needs to be translated in opposite direction of the transientTransform + // translation (representing the scroll). This is because scrolling down, which + // translates the layer content up, should result in moving the scroll thumb down. + // The amount of the translation to the scroll thumb should be such that the ratio + // of the translation to the size of the scroll port is the same as the ratio + // of the scroll amount to the size of the scrollable rect. + Matrix4x4 scrollbarTransform; + if (aScrollbar->GetScrollbarDirection() == Layer::VERTICAL) { + float scale = metrics.CalculateCompositedSizeInCssPixels().height / metrics.mScrollableRect.height; + scrollbarTransform = scrollbarTransform * Matrix4x4().Scale(1.f, 1.f / transientTransform.GetYScale(), 1.f); + scrollbarTransform = scrollbarTransform * Matrix4x4().Translate(0, -transientTransform._42 * scale, 0); + } + if (aScrollbar->GetScrollbarDirection() == Layer::HORIZONTAL) { + float scale = metrics.CalculateCompositedSizeInCssPixels().width / metrics.mScrollableRect.width; + scrollbarTransform = scrollbarTransform * Matrix4x4().Scale(1.f / transientTransform.GetXScale(), 1.f, 1.f); + scrollbarTransform = scrollbarTransform * Matrix4x4().Translate(-transientTransform._41 * scale, 0, 0); + } + + Matrix4x4 transform = scrollbarTransform * aScrollbar->GetTransform(); + + if (aScrollbarIsChild) { + // If the scrollbar layer is a child of the content it is a scrollbar for, then we + // need to do an extra untransform to cancel out the transient async transform on + // the content. This is needed because otherwise that transient async transform is + // part of the effective transform of this scrollbar, and the scrollbar will jitter + // as the content scrolls. + Matrix4x4 targetUntransform; + ToMatrix4x4(transientTransform.Inverse(), targetUntransform); + transform = transform * targetUntransform; + } + + // GetTransform already takes the pre- and post-scale into account. Since we + // will apply the pre- and post-scale again when computing the effective + // transform, we must apply the inverses here. + transform.Scale(1.0f/aScrollbar->GetPreXScale(), + 1.0f/aScrollbar->GetPreYScale(), + 1); + transform = transform * Matrix4x4().Scale(1.0f/aScrollbar->GetPostXScale(), + 1.0f/aScrollbar->GetPostYScale(), + 1); + aScrollbar->AsLayerComposite()->SetShadowTransform(transform); +} + +void +AsyncCompositionManager::ApplyAsyncTransformToScrollbar(TimeStamp aCurrentFrame, ContainerLayer* aLayer) +{ + // If this layer corresponds to a scrollbar, then there should be a layer that + // is a previous sibling or a parent that has a matching ViewID on its FrameMetrics. + // That is the content that this scrollbar is for. We pick up the transient + // async transform from that layer and use it to update the scrollbar position. + // Note that it is possible that the content layer is no longer there; in + // this case we don't need to do anything because there can't be an async + // transform on the content. + // We only apply the transform if the scroll-target layer has non-container + // children (i.e. when it has some possibly-visible content). This is to + // avoid moving scroll-bars in the situation that only a scroll information + // layer has been built for a scroll frame, as this would result in a + // disparity between scrollbars and visible content. + for (Layer* scrollTarget = aLayer->GetPrevSibling(); + scrollTarget; + scrollTarget = scrollTarget->GetPrevSibling()) { + if (LayerIsContainerForScrollbarTarget(scrollTarget, aLayer)) { + // Found a sibling that matches our criteria + ApplyAsyncTransformToScrollbarForContent(aCurrentFrame, aLayer, scrollTarget, false); + return; + } + } + + // If we didn't find a sibling, look for a parent + Layer* scrollTarget = aLayer->GetParent(); + if (scrollTarget && LayerIsContainerForScrollbarTarget(scrollTarget, aLayer)) { + ApplyAsyncTransformToScrollbarForContent(aCurrentFrame, aLayer, scrollTarget, true); + } +} + +void +AsyncCompositionManager::TransformScrollableLayer(Layer* aLayer) +{ + LayerComposite* layerComposite = aLayer->AsLayerComposite(); + ContainerLayer* container = aLayer->AsContainerLayer(); + + const FrameMetrics& metrics = container->GetFrameMetrics(); + // We must apply the resolution scale before a pan/zoom transform, so we call + // GetTransform here. + gfx3DMatrix currentTransform; + To3DMatrix(aLayer->GetTransform(), currentTransform); + Matrix4x4 oldTransform = aLayer->GetTransform(); + + gfx3DMatrix treeTransform; + + CSSToLayerScale geckoZoom = metrics.LayersPixelsPerCSSPixel(); + + LayerIntPoint scrollOffsetLayerPixels = RoundedToInt(metrics.GetScrollOffset() * geckoZoom); + + if (mIsFirstPaint) { + mContentRect = metrics.mScrollableRect; + SetFirstPaintViewport(scrollOffsetLayerPixels, + geckoZoom, + mContentRect); + mIsFirstPaint = false; + } else if (!metrics.mScrollableRect.IsEqualEdges(mContentRect)) { + mContentRect = metrics.mScrollableRect; + SetPageRect(mContentRect); + } + + // We synchronise the viewport information with Java after sending the above + // notifications, so that Java can take these into account in its response. + // Calculate the absolute display port to send to Java + LayerIntRect displayPort = RoundedToInt( + (metrics.mCriticalDisplayPort.IsEmpty() + ? metrics.mDisplayPort + : metrics.mCriticalDisplayPort + ) * geckoZoom); + displayPort += scrollOffsetLayerPixels; + + LayerMargin fixedLayerMargins(0, 0, 0, 0); + ScreenPoint offset(0, 0); + + // Ideally we would initialize userZoom to AsyncPanZoomController::CalculateResolution(metrics) + // but this causes a reftest-ipc test to fail (see bug 883646 comment 27). The reason for this + // appears to be that metrics.mZoom is poorly initialized in some scenarios. In these scenarios, + // however, we can assume there is no async zooming in progress and so the following statement + // works fine. + CSSToScreenScale userZoom(metrics.mDevPixelsPerCSSPixel * metrics.mCumulativeResolution * LayerToScreenScale(1)); + ScreenPoint userScroll = metrics.GetScrollOffset() * userZoom; + SyncViewportInfo(displayPort, geckoZoom, mLayersUpdated, + userScroll, userZoom, fixedLayerMargins, + offset); + mLayersUpdated = false; + + // Apply the render offset + mLayerManager->GetCompositor()->SetScreenRenderOffset(offset); + + // Handle transformations for asynchronous panning and zooming. We determine the + // zoom used by Gecko from the transformation set on the root layer, and we + // determine the scroll offset used by Gecko from the frame metrics of the + // primary scrollable layer. We compare this to the user zoom and scroll + // offset in the view transform we obtained from Java in order to compute the + // transformation we need to apply. + LayerToScreenScale zoomAdjust = userZoom / geckoZoom; + + LayerPoint geckoScroll(0, 0); + if (metrics.IsScrollable()) { + geckoScroll = metrics.GetScrollOffset() * geckoZoom; + } + + LayerPoint translation = (userScroll / zoomAdjust) - geckoScroll; + treeTransform = gfx3DMatrix(ViewTransform(-translation, + userZoom + / metrics.mDevPixelsPerCSSPixel + / metrics.GetParentResolution())); + + // 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. + gfx3DMatrix computedTransform = treeTransform * currentTransform; + computedTransform.Scale(1.0f/container->GetPreXScale(), + 1.0f/container->GetPreYScale(), + 1); + computedTransform.ScalePost(1.0f/container->GetPostXScale(), + 1.0f/container->GetPostYScale(), + 1); + Matrix4x4 matrix; + ToMatrix4x4(computedTransform, matrix); + layerComposite->SetShadowTransform(matrix); + NS_ASSERTION(!layerComposite->GetShadowTransformSetByAnimation(), + "overwriting animated transform!"); + + // Apply resolution scaling to the old transform - the layer tree as it is + // doesn't have the necessary transform to display correctly. + oldTransform.Scale(metrics.mResolution.scale, metrics.mResolution.scale, 1); + + // Make sure that overscroll and under-zoom are represented in the old + // transform so that fixed position content moves and scales accordingly. + // These calculations will effectively scale and offset fixed position layers + // in screen space when the compensatory transform is performed in + // AlignFixedAndStickyLayers. + ScreenRect contentScreenRect = mContentRect * userZoom; + gfxPoint3D overscrollTranslation; + if (userScroll.x < contentScreenRect.x) { + overscrollTranslation.x = contentScreenRect.x - userScroll.x; + } else if (userScroll.x + metrics.mCompositionBounds.width > contentScreenRect.XMost()) { + overscrollTranslation.x = contentScreenRect.XMost() - + (userScroll.x + metrics.mCompositionBounds.width); + } + if (userScroll.y < contentScreenRect.y) { + overscrollTranslation.y = contentScreenRect.y - userScroll.y; + } else if (userScroll.y + metrics.mCompositionBounds.height > contentScreenRect.YMost()) { + overscrollTranslation.y = contentScreenRect.YMost() - + (userScroll.y + metrics.mCompositionBounds.height); + } + oldTransform.Translate(overscrollTranslation.x, + overscrollTranslation.y, + overscrollTranslation.z); + + gfx::Size underZoomScale(1.0f, 1.0f); + if (mContentRect.width * userZoom.scale < metrics.mCompositionBounds.width) { + underZoomScale.width = (mContentRect.width * userZoom.scale) / + metrics.mCompositionBounds.width; + } + if (mContentRect.height * userZoom.scale < metrics.mCompositionBounds.height) { + underZoomScale.height = (mContentRect.height * userZoom.scale) / + metrics.mCompositionBounds.height; + } + oldTransform.Scale(underZoomScale.width, underZoomScale.height, 1); + + // Make sure fixed position layers don't move away from their anchor points + // when we're asynchronously panning or zooming + AlignFixedAndStickyLayers(aLayer, aLayer, oldTransform, fixedLayerMargins); +} + +bool +AsyncCompositionManager::TransformShadowTree(TimeStamp aCurrentFrame) +{ + PROFILER_LABEL("AsyncCompositionManager", "TransformShadowTree"); + Layer* root = mLayerManager->GetRoot(); + if (!root) { + return false; + } + + // NB: we must sample animations *before* sampling pan/zoom + // transforms. + bool wantNextFrame = SampleAnimations(root, aCurrentFrame); + + // FIXME/bug 775437: unify this interface with the ~native-fennec + // derived code + // + // Attempt to apply an async content transform to any layer that has + // an async pan zoom controller (which means that it is rendered + // async using Gecko). If this fails, fall back to transforming the + // primary scrollable layer. "Failing" here means that we don't + // find a frame that is async scrollable. Note that the fallback + // code also includes Fennec which is rendered async. Fennec uses + // its own platform-specific async rendering that is done partially + // in Gecko and partially in Java. + if (!ApplyAsyncContentTransformToTree(aCurrentFrame, root, &wantNextFrame)) { + nsAutoTArray scrollableLayers; +#ifdef MOZ_WIDGET_ANDROID + scrollableLayers.AppendElement(mLayerManager->GetPrimaryScrollableLayer()); +#else + mLayerManager->GetScrollableLayers(scrollableLayers); +#endif + + for (uint32_t i = 0; i < scrollableLayers.Length(); i++) { + if (scrollableLayers[i]) { + TransformScrollableLayer(scrollableLayers[i]); + } + } + } + + return wantNextFrame; +} + +void +AsyncCompositionManager::SetFirstPaintViewport(const LayerIntPoint& aOffset, + const CSSToLayerScale& aZoom, + const CSSRect& aCssPageRect) +{ +#ifdef MOZ_WIDGET_ANDROID + AndroidBridge::Bridge()->SetFirstPaintViewport(aOffset, aZoom, aCssPageRect); +#endif +} + +void +AsyncCompositionManager::SetPageRect(const CSSRect& aCssPageRect) +{ +#ifdef MOZ_WIDGET_ANDROID + AndroidBridge::Bridge()->SetPageRect(aCssPageRect); +#endif +} + +void +AsyncCompositionManager::SyncViewportInfo(const LayerIntRect& aDisplayPort, + const CSSToLayerScale& aDisplayResolution, + bool aLayersUpdated, + ScreenPoint& aScrollOffset, + CSSToScreenScale& aScale, + LayerMargin& aFixedLayerMargins, + ScreenPoint& aOffset) +{ +#ifdef MOZ_WIDGET_ANDROID + AndroidBridge::Bridge()->SyncViewportInfo(aDisplayPort, + aDisplayResolution, + aLayersUpdated, + aScrollOffset, + aScale, + aFixedLayerMargins, + aOffset); +#endif +} + +void +AsyncCompositionManager::SyncFrameMetrics(const ScreenPoint& aScrollOffset, + float aZoom, + const CSSRect& aCssPageRect, + bool aLayersUpdated, + const CSSRect& aDisplayPort, + const CSSToLayerScale& aDisplayResolution, + bool aIsFirstPaint, + LayerMargin& aFixedLayerMargins, + ScreenPoint& aOffset) +{ +#ifdef MOZ_WIDGET_ANDROID + AndroidBridge::Bridge()->SyncFrameMetrics(aScrollOffset, aZoom, aCssPageRect, + aLayersUpdated, aDisplayPort, + aDisplayResolution, aIsFirstPaint, + aFixedLayerMargins, aOffset); +#endif +} + +} // namespace layers +} // namespace mozilla