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(); gfx3DMatrix oldTransform = aLayer->GetTransform(); ViewTransform treeTransform; ScreenPoint scrollOffset; *aWantNextFrame |= controller->SampleContentTransformForFrame(aCurrentFrame, &treeTransform, scrollOffset); const gfx3DMatrix& rootTransform = mLayerManager->GetRoot()->GetTransform(); const FrameMetrics& metrics = container->GetFrameMetrics(); // XXX We use rootTransform instead of metrics.mResolution here because on // Fennec the resolution is set on the root layer rather than the scrollable layer. // The SyncFrameMetrics call and the paintScale variable are used on Fennec only // so it doesn't affect any other platforms. See bug 732971. CSSToLayerScale paintScale = metrics.mDevPixelsPerCSSPixel / LayerToLayoutDeviceScale(rootTransform.GetXScale(), rootTransform.GetYScale()); 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); gfx3DMatrix transform(gfx3DMatrix(treeTransform) * aLayer->GetTransform()); // 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. transform.Scale(1.0f/container->GetPreXScale(), 1.0f/container->GetPreYScale(), 1); transform.ScalePost(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. #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, fixedLayerMargins); appliedTransform = true; } return appliedTransform; }
void AsyncCompositionManager::TransformScrollableLayer(Layer* aLayer, const LayoutDeviceToLayerScale& aResolution) { 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. const gfx3DMatrix& currentTransform = aLayer->GetTransform(); gfx3DMatrix oldTransform = currentTransform; gfx3DMatrix treeTransform; CSSToLayerScale geckoZoom = metrics.mDevPixelsPerCSSPixel * aResolution; LayerIntPoint scrollOffsetLayerPixels = RoundedToInt(metrics.mScrollOffset * 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.scale * metrics.mResolution.scale); ScreenPoint userScroll = metrics.mScrollOffset * 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; LayerIntPoint geckoScroll(0, 0); if (metrics.IsScrollable()) { geckoScroll = scrollOffsetLayerPixels; } LayerPoint translation = (userScroll / zoomAdjust) - geckoScroll; treeTransform = gfx3DMatrix(ViewTransform(-translation, userZoom / metrics.mDevPixelsPerCSSPixel)); // 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); layerComposite->SetShadowTransform(computedTransform); 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(aResolution.scale, aResolution.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 // AlignFixedLayersForAnchorPoint. 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); gfxSize 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 AlignFixedLayersForAnchorPoint(aLayer, aLayer, oldTransform, fixedLayerMargins); }
virtual nsIntRegion ComputeChangeInternal(NotifySubDocInvalidationFunc aCallback, bool& aGeometryChanged) { ContainerLayer* container = mLayer->AsContainerLayer(); nsIntRegion result; bool childrenChanged = false; if (mPreXScale != container->GetPreXScale() || mPreYScale != container->GetPreYScale()) { aGeometryChanged = true; result = OldTransformedBounds(); AddRegion(result, NewTransformedBounds()); childrenChanged = true; // Can't bail out early, we need to update the child container layers } // A low frame rate is especially visible to users when scrolling, so we // particularly want to avoid unnecessary invalidation at that time. For us // here, that means avoiding unnecessary invalidation of child items when // other children are added to or removed from our container layer, since // that may be caused by children being scrolled in or out of view. We are // less concerned with children changing order. // TODO: Consider how we could avoid unnecessary invalidation when children // change order, and whether the overhead would be worth it. nsDataHashtable<nsPtrHashKey<Layer>, uint32_t> oldIndexMap(mChildren.Length()); for (uint32_t i = 0; i < mChildren.Length(); ++i) { oldIndexMap.Put(mChildren[i]->mLayer, i); } uint32_t i = 0; // cursor into the old child list mChildren for (Layer* child = container->GetFirstChild(); child; child = child->GetNextSibling()) { bool invalidateChildsCurrentArea = false; if (i < mChildren.Length()) { uint32_t childsOldIndex; if (oldIndexMap.Get(child, &childsOldIndex)) { if (childsOldIndex >= i) { // Invalidate the old areas of layers that used to be between the // current |child| and the previous |child| that was also in the // old list mChildren (if any of those children have been reordered // rather than removed, we will invalidate their new area when we // encounter them in the new list): for (uint32_t j = i; j < childsOldIndex; ++j) { AddRegion(result, mChildren[j]->OldTransformedBounds()); childrenChanged |= true; } // Invalidate any regions of the child that have changed: nsIntRegion region = mChildren[childsOldIndex]->ComputeChange(aCallback, aGeometryChanged); i = childsOldIndex + 1; if (!region.IsEmpty()) { AddRegion(result, region); childrenChanged |= true; } } else { // We've already seen this child in mChildren (which means it must // have been reordered) and invalidated its old area. We need to // invalidate its new area too: invalidateChildsCurrentArea = true; } } else { // |child| is new invalidateChildsCurrentArea = true; } } else { // |child| is new, or was reordered to a higher index invalidateChildsCurrentArea = true; } if (invalidateChildsCurrentArea) { aGeometryChanged = true; AddTransformedRegion(result, child->GetVisibleRegion(), child->GetLocalTransform()); if (aCallback) { NotifySubdocumentInvalidationRecursive(child, aCallback); } else { ClearInvalidations(child); } } childrenChanged |= invalidateChildsCurrentArea; } // Process remaining removed children. while (i < mChildren.Length()) { childrenChanged |= true; AddRegion(result, mChildren[i]->OldTransformedBounds()); i++; } if (aCallback) { aCallback(container, result); } if (childrenChanged) { container->SetChildrenChanged(true); } result.Transform(gfx::To3DMatrix(mLayer->GetLocalTransform())); return result; }