void AppendToString(std::stringstream& aStream, const Matrix4x4& m, const char* pfx, const char* sfx) { aStream << pfx; if (m.Is2D()) { Matrix matrix = m.As2D(); if (matrix.IsIdentity()) { aStream << "[ I ]"; aStream << sfx; return; } aStream << nsPrintfCString( "[ %g %g; %g %g; %g %g; ]", matrix._11, matrix._12, matrix._21, matrix._22, matrix._31, matrix._32).get(); } else { aStream << nsPrintfCString( "[ %g %g %g %g; %g %g %g %g; %g %g %g %g; %g %g %g %g; ]", m._11, m._12, m._13, m._14, m._21, m._22, m._23, m._24, m._31, m._32, m._33, m._34, m._41, m._42, m._43, m._44).get(); } aStream << sfx; }
Matrix4x4 Layer::SnapTransformTranslation(const Matrix4x4& aTransform, Matrix* aResidualTransform) { if (aResidualTransform) { *aResidualTransform = Matrix(); } Matrix matrix2D; Matrix4x4 result; if (mManager->IsSnappingEffectiveTransforms() && aTransform.Is2D(&matrix2D) && !matrix2D.HasNonTranslation() && matrix2D.HasNonIntegerTranslation()) { IntPoint snappedTranslation = RoundedToInt(matrix2D.GetTranslation()); Matrix snappedMatrix = Matrix::Translation(snappedTranslation.x, snappedTranslation.y); result = Matrix4x4::From2D(snappedMatrix); if (aResidualTransform) { // set aResidualTransform so that aResidual * snappedMatrix == matrix2D. // (I.e., appying snappedMatrix after aResidualTransform gives the // ideal transform.) *aResidualTransform = Matrix::Translation(matrix2D._31 - snappedTranslation.x, matrix2D._32 - snappedTranslation.y); } } else { result = aTransform; } return result; }
void AppendToString(std::stringstream& aStream, const Matrix4x4& m, const char* pfx, const char* sfx) { if (m.Is2D()) { Matrix matrix = m.As2D(); AppendToString(aStream, matrix, pfx, sfx); return; } aStream << pfx; aStream << nsPrintfCString( "[ %g %g %g %g; %g %g %g %g; %g %g %g %g; %g %g %g %g; ]", m._11, m._12, m._13, m._14, m._21, m._22, m._23, m._24, m._31, m._32, m._33, m._34, m._41, m._42, m._43, m._44).get(); aStream << sfx; }
static void IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame, LayerActivity* aActivity) { const nsStyleDisplay* display = aFrame->StyleDisplay(); if (!display->mSpecifiedTransform) { // The transform was removed. aActivity->mPreviousTransformScale = Nothing(); IncrementMutationCount(&aActivity->mScaleRestyleCount); return; } // Compute the new scale due to the CSS transform property. nsPresContext* presContext = aFrame->PresContext(); RuleNodeCacheConditions dummy; nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame); Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms(display->mSpecifiedTransform->mHead, aFrame->StyleContext(), presContext, dummy, refBox, presContext->AppUnitsPerCSSPixel()); Matrix transform2D; if (!transform.Is2D(&transform2D)) { // We don't attempt to handle 3D transforms; just assume the scale changed. aActivity->mPreviousTransformScale = Nothing(); IncrementMutationCount(&aActivity->mScaleRestyleCount); return; } gfxSize scale = ThebesMatrix(transform2D).ScaleFactors(true); if (aActivity->mPreviousTransformScale == Some(scale)) { return; // Nothing changed. } aActivity->mPreviousTransformScale = Some(scale); IncrementMutationCount(&aActivity->mScaleRestyleCount); }
Matrix4x4 Layer::SnapTransform(const Matrix4x4& aTransform, const gfxRect& aSnapRect, Matrix* aResidualTransform) { if (aResidualTransform) { *aResidualTransform = Matrix(); } Matrix matrix2D; Matrix4x4 result; if (mManager->IsSnappingEffectiveTransforms() && aTransform.Is2D(&matrix2D) && gfx::Size(1.0, 1.0) <= ToSize(aSnapRect.Size()) && matrix2D.PreservesAxisAlignedRectangles()) { IntPoint transformedTopLeft = RoundedToInt(matrix2D * ToPoint(aSnapRect.TopLeft())); IntPoint transformedTopRight = RoundedToInt(matrix2D * ToPoint(aSnapRect.TopRight())); IntPoint transformedBottomRight = RoundedToInt(matrix2D * ToPoint(aSnapRect.BottomRight())); Matrix snappedMatrix = gfxUtils::TransformRectToRect(aSnapRect, transformedTopLeft, transformedTopRight, transformedBottomRight); result = Matrix4x4::From2D(snappedMatrix); if (aResidualTransform && !snappedMatrix.IsSingular()) { // set aResidualTransform so that aResidual * snappedMatrix == matrix2D. // (i.e., appying snappedMatrix after aResidualTransform gives the // ideal transform. Matrix snappedMatrixInverse = snappedMatrix; snappedMatrixInverse.Invert(); *aResidualTransform = matrix2D * snappedMatrixInverse; } } else { result = aTransform; } return result; }
void LayerManagerComposite::PostProcessLayers(Layer* aLayer, nsIntRegion& aOpaqueRegion, LayerIntRegion& aVisibleRegion, const Maybe<ParentLayerIntRect>& aClipFromAncestors) { if (aLayer->Extend3DContext()) { // For layers participating 3D rendering context, their visible // region should be empty (invisible), so we pass through them // without doing anything. // Direct children of the establisher may have a clip, becaue the // item containing it; ex. of nsHTMLScrollFrame, may give it one. Maybe<ParentLayerIntRect> layerClip = aLayer->AsHostLayer()->GetShadowClipRect(); Maybe<ParentLayerIntRect> ancestorClipForChildren = IntersectMaybeRects(layerClip, aClipFromAncestors); MOZ_ASSERT(!layerClip || !aLayer->Combines3DTransformWithAncestors(), "Only direct children of the establisher could have a clip"); for (Layer* child = aLayer->GetLastChild(); child; child = child->GetPrevSibling()) { PostProcessLayers(child, aOpaqueRegion, aVisibleRegion, ancestorClipForChildren); } return; } nsIntRegion localOpaque; // Treat layers on the path to the root of the 3D rendering context as // a giant layer if it is a leaf. Matrix4x4 transform = GetAccTransformIn3DContext(aLayer); Matrix transform2d; Maybe<IntPoint> integerTranslation; // If aLayer has a simple transform (only an integer translation) then we // can easily convert aOpaqueRegion into pre-transform coordinates and include // that region. if (transform.Is2D(&transform2d)) { if (transform2d.IsIntegerTranslation()) { integerTranslation = Some(IntPoint::Truncate(transform2d.GetTranslation())); localOpaque = aOpaqueRegion; localOpaque.MoveBy(-*integerTranslation); } } // Compute a clip that's the combination of our layer clip with the clip // from our ancestors. LayerComposite* composite = static_cast<LayerComposite*>(aLayer->AsHostLayer()); Maybe<ParentLayerIntRect> layerClip = composite->GetShadowClipRect(); MOZ_ASSERT(!layerClip || !aLayer->Combines3DTransformWithAncestors(), "The layer with a clip should not participate " "a 3D rendering context"); Maybe<ParentLayerIntRect> outsideClip = IntersectMaybeRects(layerClip, aClipFromAncestors); // Convert the combined clip into our pre-transform coordinate space, so // that it can later be intersected with our visible region. // If our transform is a perspective, there's no meaningful insideClip rect // we can compute (it would need to be a cone). Maybe<LayerIntRect> insideClip; if (outsideClip && !transform.HasPerspectiveComponent()) { Matrix4x4 inverse = transform; if (inverse.Invert()) { Maybe<LayerRect> insideClipFloat = UntransformBy(ViewAs<ParentLayerToLayerMatrix4x4>(inverse), ParentLayerRect(*outsideClip), LayerRect::MaxIntRect()); if (insideClipFloat) { insideClipFloat->RoundOut(); LayerIntRect insideClipInt; if (insideClipFloat->ToIntRect(&insideClipInt)) { insideClip = Some(insideClipInt); } } } } Maybe<ParentLayerIntRect> ancestorClipForChildren; if (insideClip) { ancestorClipForChildren = Some(ViewAs<ParentLayerPixel>(*insideClip, PixelCastJustification::MovingDownToChildren)); } // Save the value of localOpaque, which currently stores the region obscured // by siblings (and uncles and such), before our descendants contribute to it. nsIntRegion obscured = localOpaque; // Recurse on our descendants, in front-to-back order. In this process: // - Occlusions are computed for them, and they contribute to localOpaque. // - They recalculate their visible regions, taking ancestorClipForChildren // into account, and accumulate them into descendantsVisibleRegion. LayerIntRegion descendantsVisibleRegion; bool hasPreserve3DChild = false; for (Layer* child = aLayer->GetLastChild(); child; child = child->GetPrevSibling()) { PostProcessLayers(child, localOpaque, descendantsVisibleRegion, ancestorClipForChildren); if (child->Extend3DContext()) { hasPreserve3DChild = true; } } // Recalculate our visible region. LayerIntRegion visible = composite->GetShadowVisibleRegion(); // If we have descendants, throw away the visible region stored on this // layer, and use the region accumulated by our descendants instead. if (aLayer->GetFirstChild() && !hasPreserve3DChild) { visible = descendantsVisibleRegion; } // Subtract any areas that we know to be opaque. if (!obscured.IsEmpty()) { visible.SubOut(LayerIntRegion::FromUnknownRegion(obscured)); } // Clip the visible region using the combined clip. if (insideClip) { visible.AndWith(*insideClip); } composite->SetShadowVisibleRegion(visible); // Transform the newly calculated visible region into our parent's space, // apply our clip to it (if any), and accumulate it into |aVisibleRegion| // for the caller to use. ParentLayerIntRegion visibleParentSpace = TransformBy( ViewAs<LayerToParentLayerMatrix4x4>(transform), visible); if (const Maybe<ParentLayerIntRect>& clipRect = composite->GetShadowClipRect()) { visibleParentSpace.AndWith(*clipRect); } aVisibleRegion.OrWith(ViewAs<LayerPixel>(visibleParentSpace, PixelCastJustification::MovingDownToChildren)); // If we have a simple transform, then we can add our opaque area into // aOpaqueRegion. if (integerTranslation && !aLayer->HasMaskLayers() && aLayer->IsOpaqueForVisibility()) { if (aLayer->IsOpaque()) { localOpaque.OrWith(composite->GetFullyRenderedRegion()); } localOpaque.MoveBy(*integerTranslation); if (layerClip) { localOpaque.AndWith(layerClip->ToUnknownRect()); } aOpaqueRegion.OrWith(localOpaque); } }
void AsyncCompositionManager::AlignFixedAndStickyLayers(Layer* aLayer, Layer* aTransformedSubtreeRoot, const Matrix4x4& aPreviousTransformForRoot, const Matrix4x4& aCurrentTransformForRoot, const LayerMargin& aFixedLayerMargins) { bool isRootFixed = aLayer->GetIsFixedPosition() && !aLayer->GetParent()->GetIsFixedPosition(); bool isStickyForSubtree = aLayer->GetIsStickyPosition() && aLayer->GetStickyScrollContainerId() == aTransformedSubtreeRoot->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) || !aCurrentTransformForRoot.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, bail. ApplyAsyncContentTransformToTree will // have already recursed on this layer and called AlignFixedAndStickyLayers // on it with its own transforms. if (aLayer->GetFrameMetrics().IsScrollable() && aLayer != aTransformedSubtreeRoot) { return; } for (Layer* child = aLayer->GetFirstChild(); child; child = child->GetNextSibling()) { AlignFixedAndStickyLayers(child, aTransformedSubtreeRoot, aPreviousTransformForRoot, aCurrentTransformForRoot, aFixedLayerMargins); } }
void BasicContainerLayer::ComputeEffectiveTransforms(const Matrix4x4& aTransformToSurface) { // We push groups for container layers if we need to, which always // are aligned in device space, so it doesn't really matter how we snap // containers. Matrix residual; Matrix4x4 idealTransform = GetLocalTransform() * aTransformToSurface; if (!Extend3DContext() && !Is3DContextLeaf()) { // For 3D transform leaked from extended parent layer. idealTransform.ProjectTo2D(); } if (!idealTransform.CanDraw2D()) { if (!Extend3DContext() || (!idealTransform.Is2D() && Creates3DContextWithExtendingChildren())) { if (!Creates3DContextWithExtendingChildren()) { idealTransform.ProjectTo2D(); } mEffectiveTransform = idealTransform; ComputeEffectiveTransformsForChildren(Matrix4x4()); ComputeEffectiveTransformForMaskLayers(Matrix4x4()); mUseIntermediateSurface = true; return; } mEffectiveTransform = idealTransform; ComputeEffectiveTransformsForChildren(idealTransform); ComputeEffectiveTransformForMaskLayers(idealTransform); mUseIntermediateSurface = false; return; } // With 2D transform or extended 3D context. Layer* child = GetFirstChild(); bool hasSingleBlendingChild = false; if (!HasMultipleChildren() && child) { hasSingleBlendingChild = child->GetMixBlendMode() != CompositionOp::OP_OVER; } /* If we have a single childand it is not blending,, it can just inherit our opacity, * otherwise we need a PushGroup and we need to mark ourselves as using * an intermediate surface so our children don't inherit our opacity * via GetEffectiveOpacity. * Having a mask layer always forces our own push group * Having a blend mode also always forces our own push group */ mUseIntermediateSurface = GetMaskLayer() || GetForceIsolatedGroup() || (GetMixBlendMode() != CompositionOp::OP_OVER && HasMultipleChildren()) || (GetEffectiveOpacity() != 1.0 && (HasMultipleChildren() || hasSingleBlendingChild)); if (!Extend3DContext()) { idealTransform.ProjectTo2D(); } mEffectiveTransform = !mUseIntermediateSurface ? idealTransform : SnapTransformTranslation(idealTransform, &residual); Matrix4x4 childTransformToSurface = (!mUseIntermediateSurface || (mUseIntermediateSurface && !Extend3DContext() /* 2D */)) ? idealTransform : Matrix4x4::From2D(residual); ComputeEffectiveTransformsForChildren(childTransformToSurface); ComputeEffectiveTransformForMaskLayers(aTransformToSurface); }