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