diff --git a/gfx/layers/AnimationInfo.cpp b/gfx/layers/AnimationInfo.cpp index 46f5897fa4c0..2d9c1cbe3360 100644 --- a/gfx/layers/AnimationInfo.cpp +++ b/gfx/layers/AnimationInfo.cpp @@ -122,6 +122,10 @@ bool AnimationInfo::StartPendingAnimations(const TimeStamp& aReadyTime) { } void AnimationInfo::TransferMutatedFlagToLayer(Layer* aLayer) { + if (mMutated) { + aLayer->Mutated(); + mMutated = false; + } } bool AnimationInfo::ApplyPendingUpdatesForThisTransaction() { diff --git a/gfx/layers/LayerManager.cpp b/gfx/layers/LayerManager.cpp index 7d4dfec9d8c1..cc20c63d9536 100644 --- a/gfx/layers/LayerManager.cpp +++ b/gfx/layers/LayerManager.cpp @@ -64,6 +64,7 @@ LayerManager::~LayerManager() = default; void LayerManager::Destroy() { mDestroyed = true; mUserData.Destroy(); + mRoot = nullptr; mPartialPrerenderedAnimations.Clear(); } diff --git a/gfx/layers/LayerManager.h b/gfx/layers/LayerManager.h index 8e22c6498ced..33db3a419079 100644 --- a/gfx/layers/LayerManager.h +++ b/gfx/layers/LayerManager.h @@ -58,6 +58,7 @@ namespace layers { class AsyncPanZoomController; class ClientLayerManager; class Layer; +class ContainerLayer; class CompositorBridgeChild; class ReadbackProcessor; class FocusTarget; @@ -198,7 +199,7 @@ class LayerManager : public WindowRenderer { /** * Can be called anytime */ - Layer* GetRoot() { return nullptr; } + Layer* GetRoot() { return mRoot; } /** * CONSTRUCTION PHASE ONLY @@ -209,6 +210,11 @@ class LayerManager : public WindowRenderer { virtual void Mutated(Layer* aLayer) {} virtual void MutatedSimple(Layer* aLayer) {} + /** + * CONSTRUCTION PHASE ONLY + * Create a ContainerLayer for this manager's layer tree. + */ + virtual already_AddRefed CreateContainerLayer() = 0; /** * Can be called anytime, from any thread. * @@ -379,6 +385,7 @@ class LayerManager : public WindowRenderer { void SetContainsSVG(bool aContainsSVG) { mContainsSVG = aContainsSVG; } protected: + RefPtr mRoot; gfx::UserData mUserData; bool mDestroyed; bool mSnapEffectiveTransforms; diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index 31d811121158..3ed2cd5d6655 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -62,6 +62,928 @@ typedef ScrollableLayerGuid::ViewID ViewID; using namespace mozilla::gfx; using namespace mozilla::Compression; +//-------------------------------------------------- +// Layer + +Layer::Layer(LayerManager* aManager, void* aImplData) + : mManager(aManager), + mParent(nullptr), + mNextSibling(nullptr), + mPrevSibling(nullptr), + mImplData(aImplData), + mUseTileSourceRect(false) +#ifdef DEBUG + , + mDebugColorIndex(0) +#endif +{ +} + +Layer::~Layer() = default; + +void Layer::SetEventRegions(const EventRegions& aRegions) { + if (mEventRegions != aRegions) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) eventregions were %s, now %s", this, + ToString(mEventRegions).c_str(), ToString(aRegions).c_str())); + mEventRegions = aRegions; + Mutated(); + } +} + +void Layer::SetCompositorAnimations( + const LayersId& aLayersId, + const CompositorAnimations& aCompositorAnimations) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) SetCompositorAnimations with id=%" PRIu64, + this, mAnimationInfo.GetCompositorAnimationsId())); + + mAnimationInfo.SetCompositorAnimations(aLayersId, aCompositorAnimations); + + Mutated(); +} + +void Layer::ClearCompositorAnimations() { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) ClearCompositorAnimations with id=%" PRIu64, + this, mAnimationInfo.GetCompositorAnimationsId())); + + mAnimationInfo.ClearAnimations(); + + Mutated(); +} + +void Layer::StartPendingAnimations(const TimeStamp& aReadyTime) { + ForEachNode(this, [&aReadyTime](Layer* layer) { + if (layer->mAnimationInfo.StartPendingAnimations(aReadyTime)) { + layer->Mutated(); + } + }); +} + +void Layer::SetAsyncPanZoomController(uint32_t aIndex, + AsyncPanZoomController* controller) { + MOZ_ASSERT(aIndex < GetScrollMetadataCount()); + // We should never be setting an APZC on a non-scrollable layer + MOZ_ASSERT(!controller || GetFrameMetrics(aIndex).IsScrollable()); + mApzcs[aIndex] = controller; +} + +AsyncPanZoomController* Layer::GetAsyncPanZoomController( + uint32_t aIndex) const { + MOZ_ASSERT(aIndex < GetScrollMetadataCount()); +#ifdef DEBUG + if (mApzcs[aIndex]) { + MOZ_ASSERT(GetFrameMetrics(aIndex).IsScrollable()); + } +#endif + return mApzcs[aIndex]; +} + +void Layer::ScrollMetadataChanged() { + mApzcs.SetLength(GetScrollMetadataCount()); +} + +std::unordered_set +Layer::ApplyPendingUpdatesToSubtree() { + ForEachNode(this, [](Layer* layer) { + layer->ApplyPendingUpdatesForThisTransaction(); + }); + // Once we're done recursing through the whole tree, clear the pending + // updates from the manager. + return Manager()->ClearPendingScrollInfoUpdate(); +} + +bool Layer::IsOpaqueForVisibility() { + return GetEffectiveOpacity() == 1.0f && + GetEffectiveMixBlendMode() == CompositionOp::OP_OVER; +} + +bool Layer::CanUseOpaqueSurface() { + // If the visible content in the layer is opaque, there is no need + // for an alpha channel. + if (GetContentFlags() & CONTENT_OPAQUE) return true; + // Also, if this layer is the bottommost layer in a container which + // doesn't need an alpha channel, we can use an opaque surface for this + // layer too. Any transparent areas must be covered by something else + // in the container. + ContainerLayer* parent = GetParent(); + return parent && parent->GetFirstChild() == this && + parent->CanUseOpaqueSurface(); +} + +// NB: eventually these methods will be defined unconditionally, and +// can be moved into Layers.h +const Maybe& Layer::GetLocalClipRect() { + return GetClipRect(); +} + +const LayerIntRegion& Layer::GetLocalVisibleRegion() { + return GetVisibleRegion(); +} + +Matrix4x4 Layer::SnapTransformTranslation(const Matrix4x4& aTransform, + Matrix* aResidualTransform) { + if (aResidualTransform) { + *aResidualTransform = Matrix(); + } + + if (!mManager->IsSnappingEffectiveTransforms()) { + return aTransform; + } + + return gfxUtils::SnapTransformTranslation(aTransform, aResidualTransform); +} + +Matrix4x4 Layer::SnapTransformTranslation3D(const Matrix4x4& aTransform, + Matrix* aResidualTransform) { + return gfxUtils::SnapTransformTranslation3D(aTransform, aResidualTransform); +} + +Matrix4x4 Layer::SnapTransform(const Matrix4x4& aTransform, + const gfxRect& aSnapRect, + Matrix* aResidualTransform) { + if (aResidualTransform) { + *aResidualTransform = Matrix(); + } + + if (!mManager->IsSnappingEffectiveTransforms()) { + return aTransform; + } + + return gfxUtils::SnapTransform(aTransform, aSnapRect, aResidualTransform); +} + +RenderTargetIntRect Layer::CalculateScissorRect( + const RenderTargetIntRect& aCurrentScissorRect) { + ContainerLayer* container = GetParent(); + ContainerLayer* containerChild = nullptr; + NS_ASSERTION(GetParent(), "This can't be called on the root!"); + + // Find the layer creating the 3D context. + while (container->Extend3DContext() && !container->UseIntermediateSurface()) { + containerChild = container; + container = container->GetParent(); + MOZ_ASSERT(container); + } + + // Find the nearest layer with a clip, or this layer. + // ContainerState::SetupScrollingMetadata() may install a clip on + // the layer. + Layer* clipLayer = containerChild && containerChild->GetLocalClipRect() + ? containerChild + : this; + + // Establish initial clip rect: it's either the one passed in, or + // if the parent has an intermediate surface, it's the extents of that + // surface. + RenderTargetIntRect currentClip; + if (container->UseIntermediateSurface()) { + currentClip.SizeTo(container->GetIntermediateSurfaceRect().Size()); + } else { + currentClip = aCurrentScissorRect; + } + + if (!clipLayer->GetLocalClipRect()) { + return currentClip; + } + + if (GetLocalVisibleRegion().IsEmpty()) { + // When our visible region is empty, our parent may not have created the + // intermediate surface that we would require for correct clipping; however, + // this does not matter since we are invisible. + // Make sure we still compute a clip rect if we want to draw checkboarding + // for this layer, since we want to do this even if the layer is invisible. + return RenderTargetIntRect(currentClip.TopLeft(), + RenderTargetIntSize(0, 0)); + } + + const RenderTargetIntRect clipRect = ViewAs( + *clipLayer->GetLocalClipRect(), + PixelCastJustification::RenderTargetIsParentLayerForRoot); + if (clipRect.IsEmpty()) { + // We might have a non-translation transform in the container so we can't + // use the code path below. + return RenderTargetIntRect(currentClip.TopLeft(), + RenderTargetIntSize(0, 0)); + } + + RenderTargetIntRect scissor = clipRect; + if (!container->UseIntermediateSurface()) { + gfx::Matrix matrix; + DebugOnly is2D = container->GetEffectiveTransform().Is2D(&matrix); + // See DefaultComputeEffectiveTransforms below + NS_ASSERTION(is2D && matrix.PreservesAxisAlignedRectangles(), + "Non preserves axis aligned transform with clipped child " + "should have forced intermediate surface"); + gfx::Rect r(scissor.X(), scissor.Y(), scissor.Width(), scissor.Height()); + gfxRect trScissor = gfx::ThebesRect(matrix.TransformBounds(r)); + trScissor.Round(); + IntRect tmp; + if (!gfxUtils::GfxRectToIntRect(trScissor, &tmp)) { + return RenderTargetIntRect(currentClip.TopLeft(), + RenderTargetIntSize(0, 0)); + } + scissor = ViewAs(tmp); + + // Find the nearest ancestor with an intermediate surface + do { + container = container->GetParent(); + } while (container && !container->UseIntermediateSurface()); + } + + if (container) { + scissor.MoveBy(-container->GetIntermediateSurfaceRect().TopLeft()); + } + return currentClip.Intersect(scissor); +} + +const ScrollMetadata& Layer::GetScrollMetadata(uint32_t aIndex) const { + MOZ_ASSERT(aIndex < GetScrollMetadataCount()); + return mScrollMetadata[aIndex]; +} + +const FrameMetrics& Layer::GetFrameMetrics(uint32_t aIndex) const { + return GetScrollMetadata(aIndex).GetMetrics(); +} + +bool Layer::HasScrollableFrameMetrics() const { + for (uint32_t i = 0; i < GetScrollMetadataCount(); i++) { + if (GetFrameMetrics(i).IsScrollable()) { + return true; + } + } + return false; +} + +bool Layer::IsScrollableWithoutContent() const { + // A scrollable container layer with no children + return AsContainerLayer() && HasScrollableFrameMetrics() && !GetFirstChild(); +} + +Matrix4x4 Layer::GetTransform() const { + Matrix4x4 transform = mSimpleAttrs.GetTransform(); + transform.PostScale(GetPostXScale(), GetPostYScale(), 1.0f); + if (const ContainerLayer* c = AsContainerLayer()) { + transform.PreScale(c->GetPreXScale(), c->GetPreYScale(), 1.0f); + } + return transform; +} + +const CSSTransformMatrix Layer::GetTransformTyped() const { + return ViewAs(GetTransform()); +} + +Matrix4x4 Layer::GetLocalTransform() { return GetTransform(); } + +const LayerToParentLayerMatrix4x4 Layer::GetLocalTransformTyped() { + return ViewAs(GetLocalTransform()); +} + +bool Layer::IsScrollbarContainer() const { + const ScrollbarData& data = GetScrollbarData(); + return (data.mScrollbarLayerType == ScrollbarLayerType::Container) + ? data.mDirection.isSome() + : false; +} + +bool Layer::HasTransformAnimation() const { + return mAnimationInfo.HasTransformAnimation(); +} + +void Layer::ApplyPendingUpdatesForThisTransaction() { + if (mPendingTransform && *mPendingTransform != mSimpleAttrs.GetTransform()) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) PendingUpdatesForThisTransaction", this)); + mSimpleAttrs.SetTransform(*mPendingTransform); + MutatedSimple(); + } + mPendingTransform = nullptr; + + if (mAnimationInfo.ApplyPendingUpdatesForThisTransaction()) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) PendingUpdatesForThisTransaction", this)); + Mutated(); + } + + for (size_t i = 0; i < mScrollMetadata.Length(); i++) { + FrameMetrics& fm = mScrollMetadata[i].GetMetrics(); + ScrollableLayerGuid::ViewID scrollId = fm.GetScrollId(); + Maybe> update = + Manager()->GetPendingScrollInfoUpdate(scrollId); + if (update) { + nsTArray infos = update.extract(); + mScrollMetadata[i].UpdatePendingScrollInfo(std::move(infos)); + Mutated(); + } + } +} + +float Layer::GetLocalOpacity() { + float opacity = mSimpleAttrs.GetOpacity(); + return std::min(std::max(opacity, 0.0f), 1.0f); +} + +float Layer::GetEffectiveOpacity() { + float opacity = GetLocalOpacity(); + for (ContainerLayer* c = GetParent(); c && !c->UseIntermediateSurface(); + c = c->GetParent()) { + opacity *= c->GetLocalOpacity(); + } + return opacity; +} + +CompositionOp Layer::GetEffectiveMixBlendMode() { + if (mSimpleAttrs.GetMixBlendMode() != CompositionOp::OP_OVER) + return mSimpleAttrs.GetMixBlendMode(); + for (ContainerLayer* c = GetParent(); c && !c->UseIntermediateSurface(); + c = c->GetParent()) { + if (c->mSimpleAttrs.GetMixBlendMode() != CompositionOp::OP_OVER) + return c->mSimpleAttrs.GetMixBlendMode(); + } + + return mSimpleAttrs.GetMixBlendMode(); +} + +Matrix4x4 Layer::ComputeTransformToPreserve3DRoot() { + Matrix4x4 transform = GetLocalTransform(); + for (Layer* layer = GetParent(); layer && layer->Extend3DContext(); + layer = layer->GetParent()) { + transform = transform * layer->GetLocalTransform(); + } + return transform; +} + +void Layer::ComputeEffectiveTransformForMaskLayers( + const gfx::Matrix4x4& aTransformToSurface) { + if (GetMaskLayer()) { + ComputeEffectiveTransformForMaskLayer(GetMaskLayer(), aTransformToSurface); + } +} + +/* static */ +void Layer::ComputeEffectiveTransformForMaskLayer( + Layer* aMaskLayer, const gfx::Matrix4x4& aTransformToSurface) { +#ifdef DEBUG + bool maskIs2D = aMaskLayer->GetTransform().CanDraw2D(); + NS_ASSERTION(maskIs2D, "How did we end up with a 3D transform here?!"); +#endif + // The mask layer can have an async transform applied to it in some + // situations, so be sure to use its GetLocalTransform() rather than + // its GetTransform(). + aMaskLayer->mEffectiveTransform = aMaskLayer->SnapTransformTranslation( + aMaskLayer->GetLocalTransform() * aTransformToSurface, nullptr); +} + +RenderTargetRect Layer::TransformRectToRenderTarget(const LayerIntRect& aRect) { + LayerRect rect(aRect); + RenderTargetRect quad = RenderTargetRect::FromUnknownRect( + GetEffectiveTransform().TransformBounds(rect.ToUnknownRect())); + return quad; +} + +bool Layer::GetVisibleRegionRelativeToRootLayer(nsIntRegion& aResult, + IntPoint* aLayerOffset) { + MOZ_ASSERT(aLayerOffset, "invalid offset pointer"); + + if (!GetParent()) { + return false; + } + + IntPoint offset; + aResult = GetLocalVisibleRegion().ToUnknownRegion(); + for (Layer* layer = this; layer; layer = layer->GetParent()) { + gfx::Matrix matrix; + if (!layer->GetLocalTransform().Is2D(&matrix) || !matrix.IsTranslation()) { + return false; + } + + // The offset of |layer| to its parent. + auto currentLayerOffset = IntPoint::Round(matrix.GetTranslation()); + + // Translate the accumulated visible region of |this| by the offset of + // |layer|. + aResult.MoveBy(currentLayerOffset.x, currentLayerOffset.y); + + // If the parent layer clips its lower layers, clip the visible region + // we're accumulating. + if (layer->GetLocalClipRect()) { + aResult.AndWith(layer->GetLocalClipRect()->ToUnknownRect()); + } + + // Now we need to walk across the list of siblings for this parent layer, + // checking to see if any of these layer trees obscure |this|. If so, + // remove these areas from the visible region as well. This will pick up + // chrome overlays like a tab modal prompt. + Layer* sibling; + for (sibling = layer->GetNextSibling(); sibling; + sibling = sibling->GetNextSibling()) { + gfx::Matrix siblingMatrix; + if (!sibling->GetLocalTransform().Is2D(&siblingMatrix) || + !siblingMatrix.IsTranslation()) { + continue; + } + + // Retreive the translation from sibling to |layer|. The accumulated + // visible region is currently oriented with |layer|. + auto siblingOffset = IntPoint::Round(siblingMatrix.GetTranslation()); + nsIntRegion siblingVisibleRegion( + sibling->GetLocalVisibleRegion().ToUnknownRegion()); + // Translate the siblings region to |layer|'s origin. + siblingVisibleRegion.MoveBy(-siblingOffset.x, -siblingOffset.y); + // Apply the sibling's clip. + // Layer clip rects are not affected by the layer's transform. + Maybe clipRect = sibling->GetLocalClipRect(); + if (clipRect) { + siblingVisibleRegion.AndWith(clipRect->ToUnknownRect()); + } + // Subtract the sibling visible region from the visible region of |this|. + aResult.SubOut(siblingVisibleRegion); + } + + // Keep track of the total offset for aLayerOffset. We use this in plugin + // positioning code. + offset += currentLayerOffset; + } + + *aLayerOffset = IntPoint(offset.x, offset.y); + return true; +} + +ContainerLayer::ContainerLayer(LayerManager* aManager, void* aImplData) + : Layer(aManager, aImplData), + mFirstChild(nullptr), + mLastChild(nullptr), + mPreXScale(1.0f), + mPreYScale(1.0f), + mInheritedXScale(1.0f), + mInheritedYScale(1.0f), + mPresShellResolution(1.0f), + mUseIntermediateSurface(false), + mMayHaveReadbackChild(false), + mChildrenChanged(false) {} + +ContainerLayer::~ContainerLayer() = default; + +bool ContainerLayer::InsertAfter(Layer* aChild, Layer* aAfter) { + if (aChild->Manager() != Manager()) { + NS_ERROR("Child has wrong manager"); + return false; + } + if (aChild->GetParent()) { + NS_ERROR("aChild already in the tree"); + return false; + } + if (aChild->GetNextSibling() || aChild->GetPrevSibling()) { + NS_ERROR("aChild already has siblings?"); + return false; + } + if (aAfter && + (aAfter->Manager() != Manager() || aAfter->GetParent() != this)) { + NS_ERROR("aAfter is not our child"); + return false; + } + + aChild->SetParent(this); + if (aAfter == mLastChild) { + mLastChild = aChild; + } + if (!aAfter) { + aChild->SetNextSibling(mFirstChild); + if (mFirstChild) { + mFirstChild->SetPrevSibling(aChild); + } + mFirstChild = aChild; + NS_ADDREF(aChild); + DidInsertChild(aChild); + return true; + } + + Layer* next = aAfter->GetNextSibling(); + aChild->SetNextSibling(next); + aChild->SetPrevSibling(aAfter); + if (next) { + next->SetPrevSibling(aChild); + } + aAfter->SetNextSibling(aChild); + NS_ADDREF(aChild); + DidInsertChild(aChild); + return true; +} + +void ContainerLayer::RemoveAllChildren() { + // Optimizes "while (mFirstChild) ContainerLayer::RemoveChild(mFirstChild);" + Layer* current = mFirstChild; + + // This is inlining DidRemoveChild() on each layer; we can skip the calls + // to NotifyPaintedLayerRemoved as it gets taken care of when as we call + // NotifyRemoved prior to removing any layers. + while (current) { + Layer* next = current->GetNextSibling(); + current = next; + } + + current = mFirstChild; + mFirstChild = nullptr; + while (current) { + MOZ_ASSERT(!current->GetPrevSibling()); + + Layer* next = current->GetNextSibling(); + current->SetParent(nullptr); + current->SetNextSibling(nullptr); + if (next) { + next->SetPrevSibling(nullptr); + } + NS_RELEASE(current); + current = next; + } +} + +// Note that ContainerLayer::RemoveAllChildren is an optimized +// version of this code; if you make changes to ContainerLayer::RemoveChild +// consider whether the matching changes need to be made to +// ContainerLayer::RemoveAllChildren +bool ContainerLayer::RemoveChild(Layer* aChild) { + if (aChild->Manager() != Manager()) { + NS_ERROR("Child has wrong manager"); + return false; + } + if (aChild->GetParent() != this) { + NS_ERROR("aChild not our child"); + return false; + } + + Layer* prev = aChild->GetPrevSibling(); + Layer* next = aChild->GetNextSibling(); + if (prev) { + prev->SetNextSibling(next); + } else { + this->mFirstChild = next; + } + if (next) { + next->SetPrevSibling(prev); + } else { + this->mLastChild = prev; + } + + aChild->SetNextSibling(nullptr); + aChild->SetPrevSibling(nullptr); + aChild->SetParent(nullptr); + + this->DidRemoveChild(aChild); + NS_RELEASE(aChild); + return true; +} + +bool ContainerLayer::RepositionChild(Layer* aChild, Layer* aAfter) { + if (aChild->Manager() != Manager()) { + NS_ERROR("Child has wrong manager"); + return false; + } + if (aChild->GetParent() != this) { + NS_ERROR("aChild not our child"); + return false; + } + if (aAfter && + (aAfter->Manager() != Manager() || aAfter->GetParent() != this)) { + NS_ERROR("aAfter is not our child"); + return false; + } + if (aChild == aAfter) { + NS_ERROR("aChild cannot be the same as aAfter"); + return false; + } + + Layer* prev = aChild->GetPrevSibling(); + Layer* next = aChild->GetNextSibling(); + if (prev == aAfter) { + // aChild is already in the correct position, nothing to do. + return true; + } + if (prev) { + prev->SetNextSibling(next); + } else { + mFirstChild = next; + } + if (next) { + next->SetPrevSibling(prev); + } else { + mLastChild = prev; + } + if (!aAfter) { + aChild->SetPrevSibling(nullptr); + aChild->SetNextSibling(mFirstChild); + if (mFirstChild) { + mFirstChild->SetPrevSibling(aChild); + } + mFirstChild = aChild; + return true; + } + + Layer* afterNext = aAfter->GetNextSibling(); + if (afterNext) { + afterNext->SetPrevSibling(aChild); + } else { + mLastChild = aChild; + } + aAfter->SetNextSibling(aChild); + aChild->SetPrevSibling(aAfter); + aChild->SetNextSibling(afterNext); + return true; +} + +bool ContainerLayer::Creates3DContextWithExtendingChildren() { + if (Extend3DContext()) { + return false; + } + for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) { + if (child->Extend3DContext()) { + return true; + } + } + return false; +} + +RenderTargetIntRect ContainerLayer::GetIntermediateSurfaceRect() { + NS_ASSERTION(mUseIntermediateSurface, "Must have intermediate surface"); + LayerIntRect bounds = GetLocalVisibleRegion().GetBounds(); + return RenderTargetIntRect::FromUnknownRect(bounds.ToUnknownRect()); +} + +bool ContainerLayer::HasMultipleChildren() { + uint32_t count = 0; + for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) { + const Maybe& clipRect = child->GetLocalClipRect(); + if (clipRect && clipRect->IsEmpty()) continue; + if (!child->Extend3DContext() && child->GetLocalVisibleRegion().IsEmpty()) + continue; + ++count; + if (count > 1) return true; + } + + return false; +} + +/** + * Collect all leaf descendants of the current 3D context. + */ +void ContainerLayer::Collect3DContextLeaves(nsTArray& aToSort) { + ForEachNode((Layer*)this, [this, &aToSort](Layer* layer) { + ContainerLayer* container = layer->AsContainerLayer(); + if (layer == this || (container && container->Extend3DContext() && + !container->UseIntermediateSurface())) { + return TraversalFlag::Continue; + } + aToSort.AppendElement(layer); + return TraversalFlag::Skip; + }); +} + +static nsTArray SortLayersWithBSPTree(nsTArray& aArray) { + std::list inputLayers; + + // Build a list of polygons to be sorted. + for (Layer* layer : aArray) { + // Ignore invisible layers. + if (!layer->IsVisible()) { + continue; + } + + const gfx::IntRect& bounds = + layer->GetLocalVisibleRegion().GetBounds().ToUnknownRect(); + + const gfx::Matrix4x4& transform = layer->GetEffectiveTransform(); + + if (transform.IsSingular()) { + // Transform cannot be inverted. + continue; + } + + gfx::Polygon polygon = gfx::Polygon::FromRect(gfx::Rect(bounds)); + + // Transform the polygon to screen space. + polygon.TransformToScreenSpace(transform); + + if (polygon.GetPoints().Length() >= 3) { + inputLayers.push_back(LayerPolygon(layer, std::move(polygon))); + } + } + + if (inputLayers.empty()) { + return nsTArray(); + } + + // Build a BSP tree from the list of polygons. + BSPTree tree(inputLayers); + + nsTArray orderedLayers(tree.GetDrawOrder()); + + // Transform the polygons back to layer space. + for (LayerPolygon& layerPolygon : orderedLayers) { + gfx::Matrix4x4 inverse = + layerPolygon.data->GetEffectiveTransform().Inverse(); + + MOZ_ASSERT(layerPolygon.geometry); + layerPolygon.geometry->TransformToLayerSpace(inverse); + } + + return orderedLayers; +} + +static nsTArray StripLayerGeometry( + const nsTArray& aLayers) { + nsTArray layers; + std::set uniqueLayers; + + for (const LayerPolygon& layerPolygon : aLayers) { + auto result = uniqueLayers.insert(layerPolygon.data); + + if (result.second) { + // Layer was added to the set. + layers.AppendElement(LayerPolygon(layerPolygon.data)); + } + } + + return layers; +} + +nsTArray ContainerLayer::SortChildrenBy3DZOrder( + SortMode aSortMode) { + AutoTArray toSort; + nsTArray drawOrder; + + for (Layer* layer = GetFirstChild(); layer; layer = layer->GetNextSibling()) { + ContainerLayer* container = layer->AsContainerLayer(); + + if (container && container->Extend3DContext() && + !container->UseIntermediateSurface()) { + // Collect 3D layers in toSort array. + container->Collect3DContextLeaves(toSort); + + // Sort the 3D layers. + if (toSort.Length() > 0) { + nsTArray sorted = SortLayersWithBSPTree(toSort); + drawOrder.AppendElements(std::move(sorted)); + + toSort.ClearAndRetainStorage(); + } + + continue; + } + + drawOrder.AppendElement(LayerPolygon(layer)); + } + + if (aSortMode == SortMode::WITHOUT_GEOMETRY) { + // Compositor does not support arbitrary layers, strip the layer geometry + // and duplicate layers. + return StripLayerGeometry(drawOrder); + } + + return drawOrder; +} + +bool ContainerLayer::AnyAncestorOrThisIs3DContextLeaf() { + Layer* parent = this; + while (parent != nullptr) { + if (parent->Is3DContextLeaf()) { + return true; + } + + parent = parent->GetParent(); + } + + return false; +} + +void ContainerLayer::DefaultComputeEffectiveTransforms( + const Matrix4x4& aTransformToSurface) { + Matrix residual; + Matrix4x4 idealTransform = GetLocalTransform() * aTransformToSurface; + + // Keep 3D transforms for leaves to keep z-order sorting correct. + if (!Extend3DContext() && !Is3DContextLeaf()) { + idealTransform.ProjectTo2D(); + } + + bool useIntermediateSurface; + if (GetMaskLayer() || GetForceIsolatedGroup()) { + useIntermediateSurface = true; +#ifdef MOZ_DUMP_PAINTING + } else if (gfxEnv::DumpPaintIntermediate() && !Extend3DContext()) { + useIntermediateSurface = true; +#endif + } else { + /* Don't use an intermediate surface for opacity when it's within a 3d + * context, since we'd rather keep the 3d effects. This matches the + * WebKit/blink behaviour, but is changing in the latest spec. + */ + float opacity = GetEffectiveOpacity(); + CompositionOp blendMode = GetEffectiveMixBlendMode(); + if ((HasMultipleChildren() || Creates3DContextWithExtendingChildren()) && + ((opacity != 1.0f && !Extend3DContext()) || + (blendMode != CompositionOp::OP_OVER))) { + useIntermediateSurface = true; + } else if ((!idealTransform.Is2D() || AnyAncestorOrThisIs3DContextLeaf()) && + Creates3DContextWithExtendingChildren()) { + useIntermediateSurface = true; + } else if (blendMode != CompositionOp::OP_OVER && + Manager()->BlendingRequiresIntermediateSurface()) { + useIntermediateSurface = true; + } else { + useIntermediateSurface = false; + gfx::Matrix contTransform; + bool checkClipRect = false; + bool checkMaskLayers = false; + + if (!idealTransform.Is2D(&contTransform)) { + // In 3D case, always check if we should use IntermediateSurface. + checkClipRect = true; + checkMaskLayers = true; + } else { + contTransform.NudgeToIntegers(); +#ifdef MOZ_GFX_OPTIMIZE_MOBILE + if (!contTransform.PreservesAxisAlignedRectangles()) { +#else + if (gfx::ThebesMatrix(contTransform).HasNonIntegerTranslation()) { +#endif + checkClipRect = true; + } + /* In 2D case, only translation and/or positive scaling can be done w/o + * using IntermediateSurface. Otherwise, when rotation or flip happen, + * we should check whether to use IntermediateSurface. + */ + if (contTransform.HasNonAxisAlignedTransform() || + contTransform.HasNegativeScaling()) { + checkMaskLayers = true; + } + } + + if (checkClipRect || checkMaskLayers) { + for (Layer* child = GetFirstChild(); child; + child = child->GetNextSibling()) { + const Maybe& clipRect = child->GetLocalClipRect(); + /* We can't (easily) forward our transform to children with a + * non-empty clip rect since it would need to be adjusted for the + * transform. See the calculations performed by CalculateScissorRect + * above. Nor for a child with a mask layer. + */ + if (checkClipRect && (clipRect && !clipRect->IsEmpty() && + (child->Extend3DContext() || + !child->GetLocalVisibleRegion().IsEmpty()))) { + useIntermediateSurface = true; + break; + } + if (checkMaskLayers && child->GetMaskLayer()) { + useIntermediateSurface = true; + break; + } + } + } + } + } + + NS_ASSERTION(!Extend3DContext() || !useIntermediateSurface, + "Can't have an intermediate surface with preserve-3d!"); + + if (useIntermediateSurface) { + mEffectiveTransform = SnapTransformTranslation(idealTransform, &residual); + } else { + mEffectiveTransform = idealTransform; + } + + // For layers extending 3d context, its ideal transform should be + // applied on children. + if (!Extend3DContext()) { + // Without this projection, non-container children would get a 3D + // transform while 2D is expected. + idealTransform.ProjectTo2D(); + } + mUseIntermediateSurface = useIntermediateSurface; + if (useIntermediateSurface) { + ComputeEffectiveTransformsForChildren(Matrix4x4::From2D(residual)); + } else { + ComputeEffectiveTransformsForChildren(idealTransform); + } + + ComputeEffectiveTransformForMaskLayers(aTransformToSurface); +} + +void ContainerLayer::ComputeEffectiveTransformsForChildren( + const Matrix4x4& aTransformToSurface) { + for (Layer* l = mFirstChild; l; l = l->GetNextSibling()) { + l->ComputeEffectiveTransforms(aTransformToSurface); + } +} + +// Note that ContainerLayer::RemoveAllChildren contains an optimized +// version of this code; if you make changes to ContainerLayer::DidRemoveChild +// consider whether the matching changes need to be made to +// ContainerLayer::RemoveAllChildren +void ContainerLayer::DidRemoveChild(Layer* aLayer) {} + +void ContainerLayer::DidInsertChild(Layer* aLayer) {} + #ifdef MOZ_DUMP_PAINTING template void WriteSnapshotToDumpFile_internal(T* aObj, DataSourceSurface* aSurf) { @@ -93,6 +1015,216 @@ void WriteSnapshotToDumpFile(Compositor* aCompositor, DrawTarget* aTarget) { } #endif +void Layer::SetDisplayListLog(const char* log) { + if (gfxUtils::DumpDisplayList()) { + mDisplayListLog = log; + } +} + +void Layer::GetDisplayListLog(nsCString& log) { + log.SetLength(0); + + if (gfxUtils::DumpDisplayList()) { + // This function returns a plain text string which consists of two things + // 1. DisplayList log. + // 2. Memory address of this layer. + // We know the target layer of each display item by information in #1. + // Here is an example of a Text display item line log in #1 + // Text p=0xa9850c00 f=0x0xaa405b00(..... + // f keeps the address of the target client layer of a display item. + // For LayerScope, display-item-to-client-layer mapping is not enough since + // LayerScope, which lives in the chrome process, knows only composite + // layers. As so, we need display-item-to-client-layer-to-layer-composite + // mapping. That's the reason we insert #2 into the log + log.AppendPrintf("0x%p\n%s", (void*)this, mDisplayListLog.get()); + } +} + +void Layer::Log(const char* aPrefix) { + if (!IsLogEnabled()) return; + + LogSelf(aPrefix); + + if (Layer* kid = GetFirstChild()) { + nsAutoCString pfx(aPrefix); + pfx += " "; + kid->Log(pfx.get()); + } + + if (Layer* next = GetNextSibling()) next->Log(aPrefix); +} + +void Layer::LogSelf(const char* aPrefix) { + if (!IsLogEnabled()) return; + + std::stringstream ss; + PrintInfo(ss, aPrefix); + MOZ_LAYERS_LOG(("%s", ss.str().c_str())); + + if (mMaskLayer) { + nsAutoCString pfx(aPrefix); + pfx += R"( \ MaskLayer )"; + mMaskLayer->LogSelf(pfx.get()); + } +} + +void Layer::PrintInfo(std::stringstream& aStream, const char* aPrefix) { + aStream << aPrefix; + aStream + << nsPrintfCString("%s%s (0x%p)", mManager->Name(), Name(), this).get(); + + if (mClipRect) { + aStream << " [clip=" << *mClipRect << "]"; + } + if (1.0 != mSimpleAttrs.GetPostXScale() || + 1.0 != mSimpleAttrs.GetPostYScale()) { + aStream << nsPrintfCString(" [postScale=%g, %g]", + mSimpleAttrs.GetPostXScale(), + mSimpleAttrs.GetPostYScale()) + .get(); + } + if (!GetBaseTransform().IsIdentity()) { + aStream << " [transform=" << GetBaseTransform() << "]"; + } + if (!GetEffectiveTransform().IsIdentity()) { + aStream << " [effective-transform=" << GetEffectiveTransform() << "]"; + } + if (GetTransformIsPerspective()) { + aStream << " [perspective]"; + } + if (!mVisibleRegion.IsEmpty()) { + aStream << " [visible=" << mVisibleRegion << "]"; + } else { + aStream << " [not visible]"; + } + if (!mEventRegions.IsEmpty()) { + aStream << " " << mEventRegions; + } + if (1.0 != GetOpacity()) { + aStream << nsPrintfCString(" [opacity=%g]", GetOpacity()).get(); + } + if (IsOpaque()) { + aStream << " [opaqueContent]"; + } + if (GetContentFlags() & CONTENT_COMPONENT_ALPHA) { + aStream << " [componentAlpha]"; + } + if (GetContentFlags() & CONTENT_BACKFACE_HIDDEN) { + aStream << " [backfaceHidden]"; + } + if (Extend3DContext()) { + aStream << " [extend3DContext]"; + } + if (Combines3DTransformWithAncestors()) { + aStream << " [combines3DTransformWithAncestors]"; + } + if (Is3DContextLeaf()) { + aStream << " [is3DContextLeaf]"; + } + if (Maybe viewId = GetAsyncZoomContainerId()) { + aStream << nsPrintfCString(" [asyncZoomContainer scrollId=%" PRIu64 "]", + *viewId) + .get(); + } + if (IsScrollbarContainer()) { + aStream << " [scrollbar]"; + } + if (GetScrollbarData().IsThumb()) { + if (Maybe thumbDirection = GetScrollbarData().mDirection) { + if (*thumbDirection == ScrollDirection::eVertical) { + aStream << nsPrintfCString(" [vscrollbar=%" PRIu64 "]", + GetScrollbarData().mTargetViewId) + .get(); + } + if (*thumbDirection == ScrollDirection::eHorizontal) { + aStream << nsPrintfCString(" [hscrollbar=%" PRIu64 "]", + GetScrollbarData().mTargetViewId) + .get(); + } + } + } + if (GetIsFixedPosition()) { + LayerPoint anchor = GetFixedPositionAnchor(); + aStream << nsPrintfCString( + " [isFixedPosition scrollId=%" PRIu64 + " sides=0x%x anchor=%s]", + GetFixedPositionScrollContainerId(), + static_cast(GetFixedPositionSides()), + ToString(anchor).c_str()) + .get(); + } + if (GetIsStickyPosition()) { + aStream << nsPrintfCString(" [isStickyPosition scrollId=%" PRIu64 + " outer=(%.3f,%.3f)-(%.3f,%.3f) " + "inner=(%.3f,%.3f)-(%.3f,%.3f)]", + GetStickyScrollContainerId(), + GetStickyScrollRangeOuter().X(), + GetStickyScrollRangeOuter().Y(), + GetStickyScrollRangeOuter().XMost(), + GetStickyScrollRangeOuter().YMost(), + GetStickyScrollRangeInner().X(), + GetStickyScrollRangeInner().Y(), + GetStickyScrollRangeInner().XMost(), + GetStickyScrollRangeInner().YMost()) + .get(); + } + if (mMaskLayer) { + aStream << nsPrintfCString(" [mMaskLayer=%p]", mMaskLayer.get()).get(); + } + for (uint32_t i = 0; i < mScrollMetadata.Length(); i++) { + if (!mScrollMetadata[i].IsDefault()) { + aStream << " [metrics" << i << "=" << mScrollMetadata[i] << "]"; + } + } + // FIXME: On the compositor thread, we don't set mAnimationInfo::mAnimations, + // All animations are transformed by AnimationHelper::ExtractAnimations() into + // mAnimationInfo.mPropertyAnimationGroups, instead. So if we want to check + // if layer trees are properly synced up across processes, we should dump + // mAnimationInfo.mPropertyAnimationGroups for the compositor thread. + // (See AnimationInfo.h for more details.) + if (!mAnimationInfo.GetAnimations().IsEmpty()) { + aStream << nsPrintfCString(" [%d animations with id=%" PRIu64 " ]", + (int)mAnimationInfo.GetAnimations().Length(), + mAnimationInfo.GetCompositorAnimationsId()) + .get(); + } +} + +bool Layer::IsBackfaceHidden() { + if (GetContentFlags() & CONTENT_BACKFACE_HIDDEN) { + Layer* container = AsContainerLayer() ? this : GetParent(); + if (container) { + // The effective transform can include non-preserve-3d parent + // transforms, since we don't always require an intermediate. + if (container->Extend3DContext() || container->Is3DContextLeaf()) { + return container->GetEffectiveTransform().IsBackfaceVisible(); + } + return container->GetBaseTransform().IsBackfaceVisible(); + } + } + return false; +} + +UniquePtr Layer::RemoveUserData(void* aKey) { + UniquePtr d(static_cast( + mUserData.Remove(static_cast(aKey)))); + return d; +} + +void ContainerLayer::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + Layer::PrintInfo(aStream, aPrefix); + if (UseIntermediateSurface()) { + aStream << " [usesTmpSurf]"; + } + if (1.0 != mPreXScale || 1.0 != mPreYScale) { + aStream + << nsPrintfCString(" [preScale=%g, %g]", mPreXScale, mPreYScale).get(); + } + aStream << nsPrintfCString(" [presShellResolution=%g]", mPresShellResolution) + .get(); +} + //-------------------------------------------------- // LayerManager @@ -108,6 +1240,7 @@ void LayerManager::Log(const char* aPrefix) { return; } + GetRoot()->Log(pfx.get()); } void LayerManager::LogSelf(const char* aPrefix) { @@ -130,6 +1263,13 @@ bool LayerManager::IsLogEnabled() { bool LayerManager::AddPendingScrollUpdateForNextTransaction( ScrollableLayerGuid::ViewID aScrollId, const ScrollPositionUpdate& aUpdateInfo) { + Layer* withPendingTransform = DepthFirstSearch( + GetRoot(), [](Layer* aLayer) { return aLayer->HasPendingTransform(); }); + if (withPendingTransform) { + return false; + } + + mPendingScrollUpdates.LookupOrInsert(aScrollId).AppendElement(aUpdateInfo); return true; } @@ -157,6 +1297,25 @@ LayerManager::ClearPendingScrollInfoUpdate() { } void SetAntialiasingFlags(Layer* aLayer, DrawTarget* aTarget) { + bool permitSubpixelAA = + !(aLayer->GetContentFlags() & Layer::CONTENT_DISABLE_SUBPIXEL_AA); + if (aTarget->IsCurrentGroupOpaque()) { + aTarget->SetPermitSubpixelAA(permitSubpixelAA); + return; + } + + const IntRect& bounds = + aLayer->GetVisibleRegion().GetBounds().ToUnknownRect(); + gfx::Rect transformedBounds = aTarget->GetTransform().TransformBounds( + gfx::Rect(Float(bounds.X()), Float(bounds.Y()), Float(bounds.Width()), + Float(bounds.Height()))); + transformedBounds.RoundOut(); + IntRect intTransformedBounds; + transformedBounds.ToIntRect(&intTransformedBounds); + permitSubpixelAA &= + !(aLayer->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA) || + aTarget->GetOpaqueRect().Contains(intTransformedBounds); + aTarget->SetPermitSubpixelAA(permitSubpixelAA); } IntRect ToOutsideIntRect(const gfxRect& aRect) { diff --git a/gfx/layers/Layers.h b/gfx/layers/Layers.h index 30d9e360a494..57d13f96d5d5 100644 --- a/gfx/layers/Layers.h +++ b/gfx/layers/Layers.h @@ -64,14 +64,72 @@ namespace layers { class Animation; class AsyncPanZoomController; +class ContainerLayer; class CompositorAnimations; class SpecificLayerAttributes; class Compositor; class TransformData; struct PropertyAnimationGroup; +#define MOZ_LAYER_DECL_NAME(n, e) \ + const char* Name() const override { return n; } \ + LayerType GetType() const override { return e; } \ + static LayerType Type() { return e; } + +/* + * Motivation: For truly smooth animation and video playback, we need to + * be able to compose frames and render them on a dedicated thread (i.e. + * off the main thread where DOM manipulation, script execution and layout + * induce difficult-to-bound latency). This requires Gecko to construct + * some kind of persistent scene structure (graph or tree) that can be + * safely transmitted across threads. We have other scenarios (e.g. mobile + * browsing) where retaining some rendered data between paints is desired + * for performance, so again we need a retained scene structure. + * + * Our retained scene structure is a layer tree. Each layer represents + * content which can be composited onto a destination surface; the root + * layer is usually composited into a window, and non-root layers are + * composited into their parent layers. Layers have attributes (e.g. + * opacity and clipping) that influence their compositing. + * + * We want to support a variety of layer implementations, including + * a simple "immediate mode" implementation that doesn't retain any + * rendered data between paints (i.e. uses cairo in just the way that + * Gecko used it before layers were introduced). But we also don't want + * to have bifurcated "layers"/"non-layers" rendering paths in Gecko. + * Therefore the layers API is carefully designed to permit maximally + * efficient implementation in an "immediate mode" style. See the + * BasicLayerManager for such an implementation. + */ + +/** + * A Layer represents anything that can be rendered onto a destination + * surface. + */ class Layer { - public: + NS_INLINE_DECL_REFCOUNTING(Layer) + + using AnimationArray = nsTArray; + + public: + // Keep these in alphabetical order + enum LayerType { + TYPE_CANVAS, + TYPE_COLOR, + TYPE_CONTAINER, + TYPE_DISPLAYITEM, + TYPE_IMAGE, + TYPE_SHADOW, + TYPE_PAINTED + }; + + /** + * Returns the LayerManager this Layer belongs to. Note that the layer + * manager might be in a destroyed state, at which point it's only + * valid to set/get user data from it. + */ + LayerManager* Manager() { return mManager; } + enum { /** * If this is set, the caller is promising that by the end of this @@ -119,6 +177,1141 @@ class Layer { */ CONTENT_SNAP_TO_GRID = 0x100 }; + /** + * CONSTRUCTION PHASE ONLY + * This lets layout make some promises about what will be drawn into the + * visible region of the PaintedLayer. This enables internal quality + * and performance optimizations. + */ + void SetContentFlags(uint32_t aFlags) { + NS_ASSERTION((aFlags & (CONTENT_OPAQUE | CONTENT_COMPONENT_ALPHA)) != + (CONTENT_OPAQUE | CONTENT_COMPONENT_ALPHA), + "Can't be opaque and require component alpha"); + if (mSimpleAttrs.SetContentFlags(aFlags)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) ContentFlags", this)); + MutatedSimple(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * Tell this layer which region will be visible. The visible region + * is a region which contains all the contents of the layer that can + * actually affect the rendering of the window. It can exclude areas + * that are covered by opaque contents of other layers, and it can + * exclude areas where this layer simply contains no content at all. + * (This can be an overapproximation to the "true" visible region.) + * + * There is no general guarantee that drawing outside the bounds of the + * visible region will be ignored. So if a layer draws outside the bounds + * of its visible region, it needs to ensure that what it draws is valid. + */ + virtual void SetVisibleRegion(const LayerIntRegion& aRegion) { + // IsEmpty is required otherwise we get invalidation glitches. + // See bug 1288464 for investigating why. + if (!mVisibleRegion.IsEqual(aRegion) || aRegion.IsEmpty()) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) VisibleRegion was %s is %s", this, + mVisibleRegion.ToString().get(), aRegion.ToString().get())); + mVisibleRegion = aRegion; + Mutated(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * Set the (sub)document metrics used to render the Layer subtree + * rooted at this. Note that a layer may have multiple FrameMetrics + * objects; calling this function will remove all of them and replace + * them with the provided FrameMetrics. See the documentation for + * SetFrameMetrics(const nsTArray&) for more details. + */ + void SetScrollMetadata(const ScrollMetadata& aScrollMetadata) { + Manager()->ClearPendingScrollInfoUpdate(); + if (mScrollMetadata.Length() != 1 || + mScrollMetadata[0] != aScrollMetadata) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) ScrollMetadata", this)); + mScrollMetadata.ReplaceElementsAt(0, mScrollMetadata.Length(), + aScrollMetadata); + ScrollMetadataChanged(); + Mutated(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * Set the (sub)document metrics used to render the Layer subtree + * rooted at this. There might be multiple metrics on this layer + * because the layer may, for example, be contained inside multiple + * nested scrolling subdocuments. In general a Layer having multiple + * ScrollMetadata objects is conceptually equivalent to having a stack + * of ContainerLayers that have been flattened into this Layer. + * See the documentation in LayerMetricsWrapper.h for a more detailed + * explanation of this conceptual equivalence. + * + * Note also that there is actually a many-to-many relationship between + * Layers and ScrollMetadata, because multiple Layers may have identical + * ScrollMetadata objects. This happens when those layers belong to the + * same scrolling subdocument and therefore end up with the same async + * transform when they are scrolled by the APZ code. + */ + void SetScrollMetadata(const nsTArray& aMetadataArray) { + Manager()->ClearPendingScrollInfoUpdate(); + if (mScrollMetadata != aMetadataArray) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) ScrollMetadata", this)); + mScrollMetadata = aMetadataArray.Clone(); + ScrollMetadataChanged(); + Mutated(); + } + } + + /* + * Compositor event handling + * ========================= + * When a touch-start event (or similar) is sent to the + * AsyncPanZoomController, it needs to decide whether the event should be sent + * to the main thread. Each layer has a list of event handling regions. When + * the compositor needs to determine how to handle a touch event, it scans the + * layer tree from top to bottom in z-order (traversing children before their + * parents). Points outside the clip region for a layer cause that layer (and + * its subtree) to be ignored. If a layer has a mask layer, and that mask + * layer's alpha value is zero at the event point, then the layer and its + * subtree should be ignored. For each layer, if the point is outside its hit + * region, we ignore the layer and move onto the next. If the point is inside + * its hit region but outside the dispatch-to-content region, we can initiate + * a gesture without consulting the content thread. Otherwise we must dispatch + * the event to content. Note that if a layer or any ancestor layer has a + * ForceEmptyHitRegion override in GetEventRegionsOverride() then the + * hit-region must be treated as empty. Similarly, if there is a + * ForceDispatchToContent override then the dispatch-to-content region must be + * treated as encompassing the entire hit region, and therefore we must + * consult the content thread before initiating a gesture. (If both flags are + * set, ForceEmptyHitRegion takes priority.) + */ + /** + * CONSTRUCTION PHASE ONLY + * Set the event handling region. + */ + void SetEventRegions(const EventRegions& aRegions); + + /** + * CONSTRUCTION PHASE ONLY + * Set the opacity which will be applied to this layer as it + * is composited to the destination. + */ + void SetOpacity(float aOpacity) { + if (mSimpleAttrs.SetOpacity(aOpacity)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) Opacity", this)); + MutatedSimple(); + } + } + + void SetMixBlendMode(gfx::CompositionOp aMixBlendMode) { + if (mSimpleAttrs.SetMixBlendMode(aMixBlendMode)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) MixBlendMode", this)); + MutatedSimple(); + } + } + + void SetForceIsolatedGroup(bool aForceIsolatedGroup) { + if (mSimpleAttrs.SetForceIsolatedGroup(aForceIsolatedGroup)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) ForceIsolatedGroup", this)); + MutatedSimple(); + } + } + + bool GetForceIsolatedGroup() const { + return mSimpleAttrs.GetForceIsolatedGroup(); + } + + /** + * CONSTRUCTION PHASE ONLY + * Set a clip rect which will be applied to this layer as it is + * composited to the destination. The coordinates are relative to + * the parent layer (i.e. the contents of this layer + * are transformed before this clip rect is applied). + * For the root layer, the coordinates are relative to the widget, + * in device pixels. + * If aRect is null no clipping will be performed. + */ + void SetClipRect(const Maybe& aRect) { + if (mClipRect) { + if (!aRect) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) ClipRect was %d,%d,%d,%d is ", + this, mClipRect->X(), mClipRect->Y(), mClipRect->Width(), + mClipRect->Height())); + mClipRect.reset(); + Mutated(); + } else { + if (!aRect->IsEqualEdges(*mClipRect)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, + ("Layer::Mutated(%p) ClipRect was %d,%d,%d,%d is %d,%d,%d,%d", + this, mClipRect->X(), mClipRect->Y(), mClipRect->Width(), + mClipRect->Height(), aRect->X(), aRect->Y(), aRect->Width(), + aRect->Height())); + mClipRect = aRect; + Mutated(); + } + } + } else { + if (aRect) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, + ("Layer::Mutated(%p) ClipRect was is %d,%d,%d,%d", this, + aRect->X(), aRect->Y(), aRect->Width(), aRect->Height())); + mClipRect = aRect; + Mutated(); + } + } + } + + /** + * CONSTRUCTION PHASE ONLY + * Set a layer to mask this layer. + * + * The mask layer should be applied using its effective transform (after it + * is calculated by ComputeEffectiveTransformForMaskLayer), this should use + * this layer's parent's transform and the mask layer's transform, but not + * this layer's. That is, the mask layer is specified relative to this layer's + * position in it's parent layer's coord space. + * Currently, only 2D translations are supported for the mask layer transform. + * + * Ownership of aMaskLayer passes to this. + * Typical use would be an ImageLayer with an alpha image used for masking. + * See also ContainerState::BuildMaskLayer in FrameLayerBuilder.cpp. + */ + void SetMaskLayer(Layer* aMaskLayer) { +#ifdef DEBUG + if (aMaskLayer) { + bool maskIs2D = aMaskLayer->GetTransform().CanDraw2D(); + NS_ASSERTION(maskIs2D, "Mask layer has invalid transform."); + } +#endif + + if (mMaskLayer != aMaskLayer) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) MaskLayer", this)); + mMaskLayer = aMaskLayer; + Mutated(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * Tell this layer what its transform should be. The transformation + * is applied when compositing the layer into its parent container. + */ + void SetBaseTransform(const gfx::Matrix4x4& aMatrix) { + NS_ASSERTION(!aMatrix.IsSingular(), + "Shouldn't be trying to draw with a singular matrix!"); + mPendingTransform = nullptr; + if (!mSimpleAttrs.SetTransform(aMatrix)) { + return; + } + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) BaseTransform", this)); + MutatedSimple(); + } + + /** + * Can be called at any time. + * + * Like SetBaseTransform(), but can be called before the next + * transform (i.e. outside an open transaction). Semantically, this + * method enqueues a new transform value to be set immediately after + * the next transaction is opened. + */ + void SetBaseTransformForNextTransaction(const gfx::Matrix4x4& aMatrix) { + mPendingTransform = mozilla::MakeUnique(aMatrix); + } + + void SetPostScale(float aXScale, float aYScale) { + if (!mSimpleAttrs.SetPostScale(aXScale, aYScale)) { + return; + } + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) PostScale", this)); + MutatedSimple(); + } + + /** + * CONSTRUCTION PHASE ONLY + * A layer is "fixed position" when it draws content from a content + * (not chrome) document, the topmost content document has a root scrollframe + * with a displayport, but the layer does not move when that displayport + * scrolls. + */ + void SetIsFixedPosition(bool aFixedPosition) { + if (mSimpleAttrs.SetIsFixedPosition(aFixedPosition)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) IsFixedPosition", this)); + MutatedSimple(); + } + } + + void SetAsyncZoomContainerId(const Maybe& aViewId) { + if (mSimpleAttrs.SetAsyncZoomContainerId(aViewId)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) AsyncZoomContainerId", this)); + MutatedSimple(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * This flag is true when the transform on the layer is a perspective + * transform. The compositor treats perspective transforms specially + * for async scrolling purposes. + */ + void SetTransformIsPerspective(bool aTransformIsPerspective) { + if (mSimpleAttrs.SetTransformIsPerspective(aTransformIsPerspective)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) TransformIsPerspective", this)); + MutatedSimple(); + } + } + // This is only called when the layer tree is updated. Do not call this from + // layout code. To add an animation to this layer, use AddAnimation. + void SetCompositorAnimations( + const LayersId& aLayersId, + const CompositorAnimations& aCompositorAnimations); + // Go through all animations in this layer and its children and, for + // any animations with a null start time, update their start time such + // that at |aReadyTime| the animation's current time corresponds to its + // 'initial current time' value. + void StartPendingAnimations(const TimeStamp& aReadyTime); + + void ClearCompositorAnimations(); + + /** + * CONSTRUCTION PHASE ONLY + * If a layer represents a fixed position element, this data is stored on the + * layer for use by the compositor. + * + * - |aScrollId| identifies the scroll frame that this element is fixed + * with respect to. + * + * - |aAnchor| is the point on the layer that is considered the "anchor" + * point, that is, the point which remains in the same position when + * compositing the layer tree with a transformation (such as when + * asynchronously scrolling and zooming). + * + * - |aSides| is the set of sides to which the element is fixed relative to. + * This is used if the viewport size is changed in the compositor and + * fixed position items need to shift accordingly. This value is made up + * combining appropriate values from mozilla::SideBits. + */ + void SetFixedPositionData(ScrollableLayerGuid::ViewID aScrollId, + const LayerPoint& aAnchor, SideBits aSides) { + if (mSimpleAttrs.SetFixedPositionData(aScrollId, aAnchor, aSides)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) FixedPositionData", this)); + MutatedSimple(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * If a layer is "sticky position", |aScrollId| holds the scroll identifier + * of the scrollable content that contains it. The difference between the two + * rectangles |aOuter| and |aInner| is treated as two intervals in each + * dimension, with the current scroll position at the origin. For each + * dimension, while that component of the scroll position lies within either + * interval, the layer should not move relative to its scrolling container. + */ + void SetStickyPositionData(ScrollableLayerGuid::ViewID aScrollId, + LayerRectAbsolute aOuter, + LayerRectAbsolute aInner) { + if (mSimpleAttrs.SetStickyPositionData(aScrollId, aOuter, aInner)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) StickyPositionData", this)); + MutatedSimple(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * If a layer is a scroll thumb container layer or a scrollbar container + * layer, set the scroll identifier of the scroll frame scrolled by + * the scrollbar, and other data related to the scrollbar. + */ + void SetScrollbarData(const ScrollbarData& aThumbData) { + if (mSimpleAttrs.SetScrollbarData(aThumbData)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) ScrollbarData", this)); + MutatedSimple(); + } + } + + // Used when forwarding transactions. Do not use at any other time. + void SetSimpleAttributes(const SimpleLayerAttributes& aAttrs) { + mSimpleAttrs = aAttrs; + } + const SimpleLayerAttributes& GetSimpleAttributes() const { + return mSimpleAttrs; + } + + // These getters can be used anytime. + float GetOpacity() { return mSimpleAttrs.GetOpacity(); } + gfx::CompositionOp GetMixBlendMode() const { + return mSimpleAttrs.GetMixBlendMode(); + } + const Maybe& GetClipRect() const { return mClipRect; } + uint32_t GetContentFlags() { return mSimpleAttrs.GetContentFlags(); } + const LayerIntRegion& GetVisibleRegion() const { return mVisibleRegion; } + const ScrollMetadata& GetScrollMetadata(uint32_t aIndex) const; + const FrameMetrics& GetFrameMetrics(uint32_t aIndex) const; + uint32_t GetScrollMetadataCount() const { return mScrollMetadata.Length(); } + const nsTArray& GetAllScrollMetadata() { + return mScrollMetadata; + } + bool HasScrollableFrameMetrics() const; + bool IsScrollableWithoutContent() const; + const EventRegions& GetEventRegions() const { return mEventRegions; } + ContainerLayer* GetParent() const { return mParent; } + Layer* GetNextSibling() { + if (mNextSibling) { + mNextSibling->CheckCanary(); + } + return mNextSibling; + } + const Layer* GetNextSibling() const { + if (mNextSibling) { + mNextSibling->CheckCanary(); + } + return mNextSibling; + } + Layer* GetPrevSibling() { return mPrevSibling; } + const Layer* GetPrevSibling() const { return mPrevSibling; } + virtual Layer* GetFirstChild() const { return nullptr; } + virtual Layer* GetLastChild() const { return nullptr; } + gfx::Matrix4x4 GetTransform() const; + // Same as GetTransform(), but returns the transform as a strongly-typed + // matrix. Eventually this will replace GetTransform(). + const CSSTransformMatrix GetTransformTyped() const; + const gfx::Matrix4x4& GetBaseTransform() const { + return mSimpleAttrs.GetTransform(); + } + // Note: these are virtual because ContainerLayerComposite overrides them. + virtual float GetPostXScale() const { return mSimpleAttrs.GetPostXScale(); } + virtual float GetPostYScale() const { return mSimpleAttrs.GetPostYScale(); } + bool GetIsFixedPosition() { return mSimpleAttrs.IsFixedPosition(); } + Maybe GetAsyncZoomContainerId() { + return mSimpleAttrs.GetAsyncZoomContainerId(); + } + bool GetTransformIsPerspective() const { + return mSimpleAttrs.GetTransformIsPerspective(); + } + bool GetIsStickyPosition() { return mSimpleAttrs.IsStickyPosition(); } + ScrollableLayerGuid::ViewID GetFixedPositionScrollContainerId() { + return mSimpleAttrs.GetFixedPositionScrollContainerId(); + } + LayerPoint GetFixedPositionAnchor() { + return mSimpleAttrs.GetFixedPositionAnchor(); + } + SideBits GetFixedPositionSides() { + return mSimpleAttrs.GetFixedPositionSides(); + } + ScrollableLayerGuid::ViewID GetStickyScrollContainerId() { + return mSimpleAttrs.GetStickyScrollContainerId(); + } + const LayerRectAbsolute& GetStickyScrollRangeOuter() { + return mSimpleAttrs.GetStickyScrollRangeOuter(); + } + const LayerRectAbsolute& GetStickyScrollRangeInner() { + return mSimpleAttrs.GetStickyScrollRangeInner(); + } + const ScrollbarData& GetScrollbarData() const { + return mSimpleAttrs.GetScrollbarData(); + } + bool IsScrollbarContainer() const; + Layer* GetMaskLayer() const { return mMaskLayer; } + bool HasPendingTransform() const { return !!mPendingTransform; } + + void CheckCanary() const { mCanary.Check(); } + + /** + * Retrieve the root level visible region for |this| taking into account + * clipping applied to parent layers of |this| as well as subtracting + * visible regions of higher siblings of this layer and each ancestor. + * + * Note translation values for offsets of visible regions and accumulated + * aLayerOffset are integer rounded using IntPoint::Round. + * + * @param aResult - the resulting visible region of this layer. + * @param aLayerOffset - this layer's total offset from the root layer. + * @return - false if during layer tree traversal a parent or sibling + * transform is found to be non-translational. This method returns early + * in this case, results will not be valid. Returns true on successful + * traversal. + */ + bool GetVisibleRegionRelativeToRootLayer(nsIntRegion& aResult, + nsIntPoint* aLayerOffset); + + // Note that all lengths in animation data are either in CSS pixels or app + // units and must be converted to device pixels by the compositor. + // Besides, this should only be called on the compositor thread. + AnimationArray& GetAnimations() { return mAnimationInfo.GetAnimations(); } + uint64_t GetCompositorAnimationsId() { + return mAnimationInfo.GetCompositorAnimationsId(); + } + nsTArray& GetPropertyAnimationGroups() { + return mAnimationInfo.GetPropertyAnimationGroups(); + } + const Maybe& GetTransformData() const { + return mAnimationInfo.GetTransformData(); + } + const LayersId& GetAnimationLayersId() const { + return mAnimationInfo.GetLayersId(); + } + + Maybe GetAnimationGeneration() const { + return mAnimationInfo.GetAnimationGeneration(); + } + + gfx::Path* CachedMotionPath() { return mAnimationInfo.CachedMotionPath(); } + + bool HasTransformAnimation() const; + + /** + * Returns the local transform for this layer: either mTransform or, + * for shadow layers, GetShadowBaseTransform(), in either case with the + * pre- and post-scales applied. + */ + gfx::Matrix4x4 GetLocalTransform(); + + /** + * Same as GetLocalTransform(), but returns a strongly-typed matrix. + * Eventually, this will replace GetLocalTransform(). + */ + const LayerToParentLayerMatrix4x4 GetLocalTransformTyped(); + + /** + * Returns the local opacity for this layer: either mOpacity or, + * for shadow layers, GetShadowOpacity() + */ + float GetLocalOpacity(); + + /** + * DRAWING PHASE ONLY + * + * Apply pending changes to layers before drawing them, if those + * pending changes haven't been overridden by later changes. + * + * Returns a list of scroll ids which had pending updates. + */ + std::unordered_set + ApplyPendingUpdatesToSubtree(); + + // Returns true if it's OK to save the contents of aLayer in an + // opaque surface (a surface without an alpha channel). + // If we can use a surface without an alpha channel, we should, because + // it will often make painting of antialiased text faster and higher + // quality. + bool CanUseOpaqueSurface(); + + SurfaceMode GetSurfaceMode() { + if (CanUseOpaqueSurface()) return SurfaceMode::SURFACE_OPAQUE; + if (GetContentFlags() & CONTENT_COMPONENT_ALPHA) + return SurfaceMode::SURFACE_COMPONENT_ALPHA; + return SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; + } + + // Returns true if this layer can be treated as opaque for visibility + // computation. A layer may be non-opaque for visibility even if it + // is not transparent, for example, if it has a mix-blend-mode. + bool IsOpaqueForVisibility(); + + /** + * This setter can be used anytime. The user data for all keys is + * initially null. Ownership pases to the layer manager. + */ + void SetUserData( + void* aKey, LayerUserData* aData, + void (*aDestroy)(void*) = LayerManager::LayerUserDataDestroy) { + mUserData.Add(static_cast(aKey), aData, aDestroy); + } + /** + * This can be used anytime. Ownership passes to the caller! + */ + UniquePtr RemoveUserData(void* aKey); + /** + * This getter can be used anytime. + */ + bool HasUserData(void* aKey) { + return mUserData.Has(static_cast(aKey)); + } + /** + * This getter can be used anytime. Ownership is retained by the layer + * manager. + */ + LayerUserData* GetUserData(void* aKey) const { + return static_cast( + mUserData.Get(static_cast(aKey))); + } + + /** + * |Disconnect()| is used by layers hooked up over IPC. It may be + * called at any time, and may not be called at all. Using an + * IPC-enabled layer after Destroy() (drawing etc.) results in a + * safe no-op; no crashy or uaf etc. + * + * XXX: this interface is essentially LayerManager::Destroy, but at + * Layer granularity. It might be beneficial to unify them. + */ + virtual void Disconnect() {} + + /** + * Dynamic cast to a ContainerLayer. Returns null if this is not + * a ContainerLayer. + */ + virtual ContainerLayer* AsContainerLayer() { return nullptr; } + virtual const ContainerLayer* AsContainerLayer() const { return nullptr; } + + // These getters can be used anytime. They return the effective + // values that should be used when drawing this layer to screen, + // accounting for this layer possibly being a shadow. + const Maybe& GetLocalClipRect(); + const LayerIntRegion& GetLocalVisibleRegion(); + + bool Extend3DContext() { + return GetContentFlags() & CONTENT_EXTEND_3D_CONTEXT; + } + bool Combines3DTransformWithAncestors() { + return GetParent() && + reinterpret_cast(GetParent())->Extend3DContext(); + } + bool Is3DContextLeaf() { + return !Extend3DContext() && Combines3DTransformWithAncestors(); + } + /** + * It is true if the user can see the back of the layer and the + * backface is hidden. The compositor should skip the layer if the + * result is true. + */ + bool IsBackfaceHidden(); + bool IsVisible() { + // For containers extending 3D context, visible region + // is meaningless, since they are just intermediate result of + // content. + return !GetLocalVisibleRegion().IsEmpty() || Extend3DContext(); + } + + /** + * Return true if current layer content is opaque. + * It does not guarantee that layer content is always opaque. + */ + virtual bool IsOpaque() { return GetContentFlags() & CONTENT_OPAQUE; } + + /** + * Returns the product of the opacities of this layer and all ancestors up + * to and excluding the nearest ancestor that has UseIntermediateSurface() + * set. + */ + float GetEffectiveOpacity(); + + /** + * Returns the blendmode of this layer. + */ + gfx::CompositionOp GetEffectiveMixBlendMode(); + + /** + * This returns the effective transform computed by + * ComputeEffectiveTransforms. Typically this is a transform that transforms + * this layer all the way to some intermediate surface or destination + * surface. For non-BasicLayers this will be a transform to the nearest + * ancestor with UseIntermediateSurface() (or to the root, if there is no + * such ancestor), but for BasicLayers it's different. + */ + const gfx::Matrix4x4& GetEffectiveTransform() const { + return mEffectiveTransform; + } + + /** + * This returns the effective transform for Layer's buffer computed by + * ComputeEffectiveTransforms. Typically this is a transform that transforms + * this layer's buffer all the way to some intermediate surface or destination + * surface. For non-BasicLayers this will be a transform to the nearest + * ancestor with UseIntermediateSurface() (or to the root, if there is no + * such ancestor), but for BasicLayers it's different. + * + * By default, its value is same to GetEffectiveTransform(). + * When ImageLayer is rendered with ScaleMode::STRETCH, + * it becomes different from GetEffectiveTransform(). + */ + virtual const gfx::Matrix4x4& GetEffectiveTransformForBuffer() const { + return mEffectiveTransform; + } + + /** + * If the current layers participates in a preserve-3d + * context (returns true for Combines3DTransformWithAncestors), + * returns the combined transform up to the preserve-3d (nearest + * ancestor that doesn't Extend3DContext()). Otherwise returns + * the local transform. + */ + gfx::Matrix4x4 ComputeTransformToPreserve3DRoot(); + + /** + * @param aTransformToSurface the composition of the transforms + * from the parent layer (if any) to the destination pixel grid. + * + * Computes mEffectiveTransform for this layer and all its descendants. + * mEffectiveTransform transforms this layer up to the destination + * pixel grid (whatever aTransformToSurface is relative to). + * + * We promise that when this is called on a layer, all ancestor layers + * have already had ComputeEffectiveTransforms called. + */ + virtual void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) = 0; + + /** + * Computes the effective transform for mask layers, if this layer has any. + */ + void ComputeEffectiveTransformForMaskLayers( + const gfx::Matrix4x4& aTransformToSurface); + static void ComputeEffectiveTransformForMaskLayer( + Layer* aMaskLayer, const gfx::Matrix4x4& aTransformToSurface); + + /** + * Calculate the scissor rect required when rendering this layer. + * Returns a rectangle relative to the intermediate surface belonging to the + * nearest ancestor that has an intermediate surface, or relative to the root + * viewport if no ancestor has an intermediate surface, corresponding to the + * clip rect for this layer intersected with aCurrentScissorRect. + */ + RenderTargetIntRect CalculateScissorRect( + const RenderTargetIntRect& aCurrentScissorRect); + + virtual const char* Name() const = 0; + virtual LayerType GetType() const = 0; + + /** + * Only the implementation should call this. This is per-implementation + * private data. Normally, all layers with a given layer manager + * use the same type of ImplData. + */ + void* ImplData() { return mImplData; } + + /** + * Only the implementation should use these methods. + */ + void SetParent(ContainerLayer* aParent) { mParent = aParent; } + void SetNextSibling(Layer* aSibling) { mNextSibling = aSibling; } + void SetPrevSibling(Layer* aSibling) { mPrevSibling = aSibling; } + + /** + * Log information about this layer manager and its managed tree to + * the NSPR log (if enabled for "Layers"). + */ + void Log(const char* aPrefix = ""); + /** + * Log information about just this layer manager itself to the NSPR + * log (if enabled for "Layers"). + */ + void LogSelf(const char* aPrefix = ""); + + // Print interesting information about this into aStream. Internally + // used to implement Dump*() and Log*(). If subclasses have + // additional interesting properties, they should override this with + // an implementation that first calls the base implementation then + // appends additional info to aTo. + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix); + + /** + * Store display list log. + */ + void SetDisplayListLog(const char* log); + + /** + * Return display list log. + */ + void GetDisplayListLog(nsCString& log); + + static bool IsLogEnabled() { return LayerManager::IsLogEnabled(); } + + /** + * Returns the current area of the layer (in layer-space coordinates) + * marked as needed to be recomposited. + */ + const virtual gfx::TiledIntRegion& GetInvalidRegion() { + return mInvalidRegion; + } + void AddInvalidRegion(const nsIntRegion& aRegion) { + mInvalidRegion.Add(aRegion); + } + + /** + * Mark the entirety of the layer's visible region as being invalid. + */ + void SetInvalidRectToVisibleRegion() { + mInvalidRegion.SetEmpty(); + mInvalidRegion.Add(GetVisibleRegion().ToUnknownRegion()); + } + + /** + * Adds to the current invalid rect. + */ + void AddInvalidRect(const gfx::IntRect& aRect) { mInvalidRegion.Add(aRect); } + + /** + * Clear the invalid rect, marking the layer as being identical to what is + * currently composited. + */ + virtual void ClearInvalidRegion() { mInvalidRegion.SetEmpty(); } + + // These functions allow attaching an AsyncPanZoomController to this layer, + // and can be used anytime. + // A layer has an APZC at index aIndex only-if + // GetFrameMetrics(aIndex).IsScrollable(); attempting to get an APZC for a + // non-scrollable metrics will return null. The reverse is also generally true + // (that if GetFrameMetrics(aIndex).IsScrollable() is true, then the layer + // will have an APZC). However, it only holds on the the compositor-side layer + // tree, and only after the APZ code has had a chance to rebuild its internal + // hit-testing tree using the layer tree. Also, it may not hold in certain + // "exceptional" scenarios such as if the layer tree doesn't have a + // GeckoContentController registered for it, or if there is a malicious + // content process trying to trip up the compositor over IPC. The aIndex for + // these functions must be less than GetScrollMetadataCount(). + void SetAsyncPanZoomController(uint32_t aIndex, + AsyncPanZoomController* controller); + AsyncPanZoomController* GetAsyncPanZoomController(uint32_t aIndex) const; + // The ScrollMetadataChanged function is used internally to ensure the APZC + // array length matches the frame metrics array length. + + virtual void ClearCachedResources() {} + + virtual bool SupportsAsyncUpdate() { return false; } + + private: + void ScrollMetadataChanged(); + + public: + void ApplyPendingUpdatesForThisTransaction(); + +#ifdef DEBUG + void SetDebugColorIndex(uint32_t aIndex) { mDebugColorIndex = aIndex; } + uint32_t GetDebugColorIndex() { return mDebugColorIndex; } +#endif + + void Mutated() { mManager->Mutated(this); } + void MutatedSimple() { mManager->MutatedSimple(this); } + + virtual int32_t GetMaxLayerSize() { return Manager()->GetMaxTextureSize(); } + + RenderTargetRect TransformRectToRenderTarget(const LayerIntRect& aRect); + + /** + * Add debugging information to the layer dump. + */ + void AddExtraDumpInfo(const nsACString& aStr) { +#ifdef MOZ_DUMP_PAINTING + mExtraDumpInfo.AppendElement(aStr); +#endif + } + + /** + * Clear debugging information. Useful for recycling. + */ + void ClearExtraDumpInfo() { +#ifdef MOZ_DUMP_PAINTING + mExtraDumpInfo.Clear(); +#endif + } + + AnimationInfo& GetAnimationInfo() { return mAnimationInfo; } + + protected: + Layer(LayerManager* aManager, void* aImplData); + + // Protected destructor, to discourage deletion outside of Release(): + virtual ~Layer(); + + /** + * We can snap layer transforms for two reasons: + * 1) To avoid unnecessary resampling when a transform is a translation + * by a non-integer number of pixels. + * Snapping the translation to an integer number of pixels avoids + * blurring the layer and can be faster to composite. + * 2) When a layer is used to render a rectangular object, we need to + * emulate the rendering of rectangular inactive content and snap the + * edges of the rectangle to pixel boundaries. This is both to ensure + * layer rendering is consistent with inactive content rendering, and to + * avoid seams. + * This function implements type 1 snapping. If aTransform is a 2D + * translation, and this layer's layer manager has enabled snapping + * (which is the default), return aTransform with the translation snapped + * to nearest pixels. Otherwise just return aTransform. Call this when the + * layer does not correspond to a single rectangular content object. + * This function does not try to snap if aTransform has a scale, because in + * that case resampling is inevitable and there's no point in trying to + * avoid it. In fact snapping can cause problems because pixel edges in the + * layer's content can be rendered unpredictably (jiggling) as the scale + * interacts with the snapping of the translation, especially with animated + * transforms. + * @param aResidualTransform a transform to apply before the result transform + * in order to get the results to completely match aTransform. + */ + gfx::Matrix4x4 SnapTransformTranslation(const gfx::Matrix4x4& aTransform, + gfx::Matrix* aResidualTransform); + gfx::Matrix4x4 SnapTransformTranslation3D(const gfx::Matrix4x4& aTransform, + gfx::Matrix* aResidualTransform); + /** + * See comment for SnapTransformTranslation. + * This function implements type 2 snapping. If aTransform is a translation + * and/or scale, transform aSnapRect by aTransform, snap to pixel boundaries, + * and return the transform that maps aSnapRect to that rect. Otherwise + * just return aTransform. + * @param aSnapRect a rectangle whose edges should be snapped to pixel + * boundaries in the destination surface. + * @param aResidualTransform a transform to apply before the result transform + * in order to get the results to completely match aTransform. + */ + gfx::Matrix4x4 SnapTransform(const gfx::Matrix4x4& aTransform, + const gfxRect& aSnapRect, + gfx::Matrix* aResidualTransform); + + LayerManager* mManager; + ContainerLayer* mParent; + Layer* mNextSibling; + Layer* mPrevSibling; + void* mImplData; + RefPtr mMaskLayer; + nsTArray> mAncestorMaskLayers; + // Look for out-of-bound in the middle of the structure + mozilla::CorruptionCanary mCanary; + gfx::UserData mUserData; + SimpleLayerAttributes mSimpleAttrs; + LayerIntRegion mVisibleRegion; + nsTArray mScrollMetadata; + EventRegions mEventRegions; + // A mutation of |mTransform| that we've queued to be applied at the + // end of the next transaction (if nothing else overrides it in the + // meantime). + UniquePtr mPendingTransform; + gfx::Matrix4x4 mEffectiveTransform; + AnimationInfo mAnimationInfo; + Maybe mClipRect; + gfx::IntRect mTileSourceRect; + gfx::TiledIntRegion mInvalidRegion; + nsTArray> mApzcs; + bool mUseTileSourceRect; +#ifdef DEBUG + uint32_t mDebugColorIndex; +#endif +#ifdef MOZ_DUMP_PAINTING + nsTArray mExtraDumpInfo; +#endif + // Store display list log. + nsCString mDisplayListLog; +}; + +/** + * A Layer which other layers render into. It holds references to its + * children. + */ +class ContainerLayer : public Layer { + public: + virtual ~ContainerLayer(); + + /** + * CONSTRUCTION PHASE ONLY + * Insert aChild into the child list of this container. aChild must + * not be currently in any child list or the root for the layer manager. + * If aAfter is non-null, it must be a child of this container and + * we insert after that layer. If it's null we insert at the start. + */ + virtual bool InsertAfter(Layer* aChild, Layer* aAfter); + /** + * CONSTRUCTION PHASE ONLY + * Remove aChild from the child list of this container. aChild must + * be a child of this container. + */ + virtual bool RemoveChild(Layer* aChild); + /** + * CONSTRUCTION PHASE ONLY + * Reposition aChild from the child list of this container. aChild must + * be a child of this container. + * If aAfter is non-null, it must be a child of this container and we + * reposition after that layer. If it's null, we reposition at the start. + */ + virtual bool RepositionChild(Layer* aChild, Layer* aAfter); + + void SetPreScale(float aXScale, float aYScale) { + if (mPreXScale == aXScale && mPreYScale == aYScale) { + return; + } + + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) PreScale", this)); + mPreXScale = aXScale; + mPreYScale = aYScale; + Mutated(); + } + + void SetInheritedScale(float aXScale, float aYScale) { + if (mInheritedXScale == aXScale && mInheritedYScale == aYScale) { + return; + } + + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) InheritedScale", this)); + mInheritedXScale = aXScale; + mInheritedYScale = aYScale; + Mutated(); + } + + void SetScaleToResolution(float aResolution) { + if (mPresShellResolution == aResolution) { + return; + } + + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) ScaleToResolution", this)); + mPresShellResolution = aResolution; + Mutated(); + } + + enum class SortMode { + WITH_GEOMETRY, + WITHOUT_GEOMETRY, + }; + + nsTArray SortChildrenBy3DZOrder(SortMode aSortMode); + + ContainerLayer* AsContainerLayer() override { return this; } + const ContainerLayer* AsContainerLayer() const override { return this; } + + // These getters can be used anytime. + Layer* GetFirstChild() const override { return mFirstChild; } + Layer* GetLastChild() const override { return mLastChild; } + float GetPreXScale() const { return mPreXScale; } + float GetPreYScale() const { return mPreYScale; } + float GetInheritedXScale() const { return mInheritedXScale; } + float GetInheritedYScale() const { return mInheritedYScale; } + float GetPresShellResolution() const { return mPresShellResolution; } + + MOZ_LAYER_DECL_NAME("ContainerLayer", TYPE_CONTAINER) + + /** + * ContainerLayer backends need to override ComputeEffectiveTransforms + * since the decision about whether to use a temporary surface for the + * container is backend-specific. ComputeEffectiveTransforms must also set + * mUseIntermediateSurface. + */ + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override = 0; + + /** + * Call this only after ComputeEffectiveTransforms has been invoked + * on this layer. + * Returns true if this will use an intermediate surface. This is largely + * backend-dependent, but it affects the operation of GetEffectiveOpacity(). + */ + bool UseIntermediateSurface() { return mUseIntermediateSurface; } + + /** + * Returns the rectangle covered by the intermediate surface, + * in this layer's coordinate system. + * + * NOTE: Since this layer has an intermediate surface it follows + * that LayerPixel == RenderTargetPixel + */ + RenderTargetIntRect GetIntermediateSurfaceRect(); + + /** + * Returns true if this container has more than one non-empty child + */ + bool HasMultipleChildren(); + + void SetChildrenChanged(bool aVal) { mChildrenChanged = aVal; } + + // If |aRect| is null, the entire layer should be considered invalid for + // compositing. + virtual void SetInvalidCompositeRect(const gfx::IntRect* aRect) {} + + protected: + friend class ReadbackProcessor; + + // Note that this is not virtual, and is based on the implementation of + // ContainerLayer::RemoveChild, so it should only be called where you would + // want to explicitly call the base class implementation of RemoveChild; + // e.g., while (mFirstChild) ContainerLayer::RemoveChild(mFirstChild); + void RemoveAllChildren(); + + void DidInsertChild(Layer* aLayer); + void DidRemoveChild(Layer* aLayer); + + bool AnyAncestorOrThisIs3DContextLeaf(); + + void Collect3DContextLeaves(nsTArray& aToSort); + + // Collects child layers that do not extend 3D context. For ContainerLayers + // that do extend 3D context, the 3D context leaves are collected. + nsTArray CollectChildren() { + nsTArray children; + + for (Layer* layer = GetFirstChild(); layer; + layer = layer->GetNextSibling()) { + ContainerLayer* container = layer->AsContainerLayer(); + + if (container && container->Extend3DContext() && + !container->UseIntermediateSurface()) { + container->Collect3DContextLeaves(children); + } else { + children.AppendElement(layer); + } + } + + return children; + } + + ContainerLayer(LayerManager* aManager, void* aImplData); + + /** + * A default implementation of ComputeEffectiveTransforms for use by OpenGL + * and D3D. + */ + void DefaultComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface); + + /** + * Loops over the children calling ComputeEffectiveTransforms on them. + */ + void ComputeEffectiveTransformsForChildren( + const gfx::Matrix4x4& aTransformToSurface); + + virtual void PrintInfo(std::stringstream& aStream, + const char* aPrefix) override; + + /** + * True for if the container start a new 3D context extended by one + * or more children. + */ + bool Creates3DContextWithExtendingChildren(); + + Layer* mFirstChild; + Layer* mLastChild; + float mPreXScale; + float mPreYScale; + // The resolution scale inherited from the parent layer. This will already + // be part of mTransform. + float mInheritedXScale; + float mInheritedYScale; + // For layers corresponding to an nsDisplayAsyncZoom, the resolution of the + // associated pres shell; for other layers, 1.0. + float mPresShellResolution; + bool mUseIntermediateSurface; + bool mMayHaveReadbackChild; + // This is updated by ComputeDifferences. This will be true if we need to + // invalidate the intermediate surface. + bool mChildrenChanged; }; void SetAntialiasingFlags(Layer* aLayer, gfx::DrawTarget* aTarget); diff --git a/gfx/layers/ipc/CompositorBridgeParent.cpp b/gfx/layers/ipc/CompositorBridgeParent.cpp index 19f9513bc3cd..9d26117a2e42 100644 --- a/gfx/layers/ipc/CompositorBridgeParent.cpp +++ b/gfx/layers/ipc/CompositorBridgeParent.cpp @@ -1495,6 +1495,7 @@ ScopedLayerTreeRegistration::ScopedLayerTreeRegistration( : mLayersId(aLayersId) { EnsureLayerTreeMapReady(); MonitorAutoLock lock(*sIndirectLayerTreesLock); + sIndirectLayerTrees[aLayersId].mRoot = nullptr; sIndirectLayerTrees[aLayersId].mController = aController; } diff --git a/gfx/layers/ipc/CompositorBridgeParent.h b/gfx/layers/ipc/CompositorBridgeParent.h index 88b565ec6161..dfa4ab0ecbfc 100644 --- a/gfx/layers/ipc/CompositorBridgeParent.h +++ b/gfx/layers/ipc/CompositorBridgeParent.h @@ -479,6 +479,7 @@ class CompositorBridgeParent final : public CompositorBridgeParentBase, struct LayerTreeState { LayerTreeState(); ~LayerTreeState(); + RefPtr mRoot; RefPtr mController; APZCTreeManagerParent* mApzcTreeManagerParent; RefPtr mParent; diff --git a/gfx/tests/gtest/TestLayers.cpp b/gfx/tests/gtest/TestLayers.cpp index 77b5e368b0ea..e27d65737b38 100644 --- a/gfx/tests/gtest/TestLayers.cpp +++ b/gfx/tests/gtest/TestLayers.cpp @@ -14,6 +14,21 @@ using namespace mozilla; using namespace mozilla::gfx; using namespace mozilla::layers; +class TestContainerLayer : public ContainerLayer { + public: + explicit TestContainerLayer(LayerManager* aManager) + : ContainerLayer(aManager, nullptr) {} + + virtual const char* Name() const { return "TestContainerLayer"; } + + virtual LayerType GetType() const { return TYPE_CONTAINER; } + + virtual void ComputeEffectiveTransforms( + const Matrix4x4& aTransformToSurface) { + DefaultComputeEffectiveTransforms(aTransformToSurface); + } +}; + class TestLayerManager : public LayerManager { public: TestLayerManager() : LayerManager() {} @@ -21,6 +36,10 @@ class TestLayerManager : public LayerManager { virtual bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) { return false; } + virtual already_AddRefed CreateContainerLayer() { + RefPtr layer = new TestContainerLayer(this); + return layer.forget(); + } virtual void GetBackendName(nsAString& aName) {} virtual LayersBackend GetBackendType() { return LayersBackend::LAYERS_BASIC; } virtual bool BeginTransaction(const nsCString& = nsCString()) { return true; } @@ -37,3 +56,68 @@ class TestUserData : public LayerUserData { MOCK_METHOD0(Die, void()); virtual ~TestUserData() { Die(); } }; + +TEST(Layers, LayerConstructor) +{ TestContainerLayer layer(nullptr); } + +TEST(Layers, Defaults) +{ + TestContainerLayer layer(nullptr); + ASSERT_EQ(1.0, layer.GetOpacity()); + ASSERT_EQ(1.0f, layer.GetPostXScale()); + ASSERT_EQ(1.0f, layer.GetPostYScale()); + + ASSERT_EQ(nullptr, layer.GetNextSibling()); + ASSERT_EQ(nullptr, layer.GetPrevSibling()); + ASSERT_EQ(nullptr, layer.GetFirstChild()); + ASSERT_EQ(nullptr, layer.GetLastChild()); +} + +TEST(Layers, Transform) +{ + TestContainerLayer layer(nullptr); + + Matrix4x4 identity; + ASSERT_EQ(true, identity.IsIdentity()); + + ASSERT_EQ(identity, layer.GetTransform()); +} + +TEST(Layers, UserData) +{ + UniquePtr layerPtr(new TestContainerLayer(nullptr)); + TestContainerLayer& layer = *layerPtr; + + void* key1 = (void*)1; + void* key2 = (void*)2; + void* key3 = (void*)3; + + ASSERT_EQ(nullptr, layer.GetUserData(key1)); + ASSERT_EQ(nullptr, layer.GetUserData(key2)); + ASSERT_EQ(nullptr, layer.GetUserData(key3)); + + TestUserData* data1 = new TestUserData; + TestUserData* data2 = new TestUserData; + TestUserData* data3 = new TestUserData; + + layer.SetUserData(key1, data1); + layer.SetUserData(key2, data2); + layer.SetUserData(key3, data3); + + // Also checking that the user data is returned but not free'd + UniquePtr d1(layer.RemoveUserData(key1)); + UniquePtr d2(layer.RemoveUserData(key2)); + UniquePtr d3(layer.RemoveUserData(key3)); + ASSERT_EQ(data1, d1.get()); + ASSERT_EQ(data2, d2.get()); + ASSERT_EQ(data3, d3.get()); + + layer.SetUserData(key1, d1.release()); + layer.SetUserData(key2, d2.release()); + layer.SetUserData(key3, d3.release()); + + // Layer has ownership of data1-3, check that they are destroyed + EXPECT_CALL(*data1, Die()); + EXPECT_CALL(*data2, Die()); + EXPECT_CALL(*data3, Die()); +} diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 992136f23959..912752632025 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -8970,6 +8970,23 @@ Maybe nsLayoutUtils::GetRootMetadata( return Nothing(); } +/* static */ +bool nsLayoutUtils::ContainsMetricsWithId(const Layer* aLayer, + const ViewID& aScrollId) { + for (uint32_t i = aLayer->GetScrollMetadataCount(); i > 0; i--) { + if (aLayer->GetFrameMetrics(i - 1).GetScrollId() == aScrollId) { + return true; + } + } + for (Layer* child = aLayer->GetFirstChild(); child; + child = child->GetNextSibling()) { + if (ContainsMetricsWithId(child, aScrollId)) { + return true; + } + } + return false; +} + /* static */ StyleTouchAction nsLayoutUtils::GetTouchActionFromFrame(nsIFrame* aFrame) { if (!aFrame) { diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 37d62fcadd15..142212a767be 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -2794,6 +2794,13 @@ class nsLayoutUtils { static nsMargin ScrollbarAreaToExcludeFromCompositionBoundsFor( const nsIFrame* aScrollFrame); + /** + * Looks in the layer subtree rooted at aLayer for a metrics with scroll id + * aScrollId. Returns true if such is found. + */ + static bool ContainsMetricsWithId(const Layer* aLayer, + const ViewID& aScrollId); + static bool ShouldUseNoScriptSheet(mozilla::dom::Document*); static bool ShouldUseNoFramesSheet(mozilla::dom::Document*); diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp index 773cb4c94153..0d22052d5e72 100644 --- a/layout/painting/nsDisplayList.cpp +++ b/layout/painting/nsDisplayList.cpp @@ -2074,6 +2074,21 @@ WebRenderLayerManager* nsDisplayListBuilder::GetWidgetLayerManager( return nullptr; } +// Find the layer which should house the root scroll metadata for a given +// layer tree. This is the async zoom container layer if there is one, +// otherwise it's the root layer. +Layer* GetLayerForRootMetadata(Layer* aRootLayer, + ScrollableLayerGuid::ViewID aRootScrollId) { + Layer* asyncZoomContainer = DepthFirstSearch( + aRootLayer, [aRootScrollId](Layer* aLayer) { + if (auto id = aLayer->GetAsyncZoomContainerId()) { + return *id == aRootScrollId; + } + return false; + }); + return asyncZoomContainer ? asyncZoomContainer : aRootLayer; +} + void nsDisplayList::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, int32_t aAppUnitsPerDevPixel) { FlattenedDisplayListIterator iter(aBuilder, this);