Example #1
0
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;
}
Example #2
0
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;
}
Example #3
0
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);
}
Example #5
0
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);
}