diff options
Diffstat (limited to 'gfx/layers/wr/ClipManager.cpp')
-rw-r--r-- | gfx/layers/wr/ClipManager.cpp | 424 |
1 files changed, 424 insertions, 0 deletions
diff --git a/gfx/layers/wr/ClipManager.cpp b/gfx/layers/wr/ClipManager.cpp new file mode 100644 index 0000000000..c1347d4210 --- /dev/null +++ b/gfx/layers/wr/ClipManager.cpp @@ -0,0 +1,424 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/ClipManager.h" + +#include "DisplayItemClipChain.h" +#include "FrameMetrics.h" +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "nsDisplayList.h" +#include "nsStyleStructInlines.h" +#include "UnitTransforms.h" + +// clang-format off +#define CLIP_LOG(...) +//#define CLIP_LOG(...) printf_stderr("CLIP: " __VA_ARGS__) +//#define CLIP_LOG(...) if (XRE_IsContentProcess()) printf_stderr("CLIP: " __VA_ARGS__) +// clang-format on + +namespace mozilla { +namespace layers { + +ClipManager::ClipManager() : mManager(nullptr), mBuilder(nullptr) {} + +void ClipManager::BeginBuild(WebRenderLayerManager* aManager, + wr::DisplayListBuilder& aBuilder) { + MOZ_ASSERT(!mManager); + mManager = aManager; + MOZ_ASSERT(!mBuilder); + mBuilder = &aBuilder; + MOZ_ASSERT(mCacheStack.empty()); + mCacheStack.emplace(); + MOZ_ASSERT(mASROverride.empty()); + MOZ_ASSERT(mItemClipStack.empty()); +} + +void ClipManager::EndBuild() { + mBuilder = nullptr; + mManager = nullptr; + mCacheStack.pop(); + MOZ_ASSERT(mCacheStack.empty()); + MOZ_ASSERT(mASROverride.empty()); + MOZ_ASSERT(mItemClipStack.empty()); +} + +void ClipManager::BeginList(const StackingContextHelper& aStackingContext) { + if (aStackingContext.AffectsClipPositioning()) { + if (aStackingContext.ReferenceFrameId()) { + PushOverrideForASR( + mItemClipStack.empty() ? nullptr : mItemClipStack.top().mASR, + aStackingContext.ReferenceFrameId().ref()); + } else { + // Start a new cache + mCacheStack.emplace(); + } + } + + ItemClips clips(nullptr, nullptr, false); + if (!mItemClipStack.empty()) { + clips.CopyOutputsFrom(mItemClipStack.top()); + } + + if (aStackingContext.ReferenceFrameId()) { + clips.mScrollId = aStackingContext.ReferenceFrameId().ref(); + } + + mItemClipStack.push(clips); +} + +void ClipManager::EndList(const StackingContextHelper& aStackingContext) { + MOZ_ASSERT(!mItemClipStack.empty()); + mBuilder->SetClipChainLeaf(Nothing()); + mItemClipStack.pop(); + + if (aStackingContext.AffectsClipPositioning()) { + if (aStackingContext.ReferenceFrameId()) { + PopOverrideForASR(mItemClipStack.empty() ? nullptr + : mItemClipStack.top().mASR); + } else { + MOZ_ASSERT(!mCacheStack.empty()); + mCacheStack.pop(); + } + } +} + +void ClipManager::PushOverrideForASR(const ActiveScrolledRoot* aASR, + const wr::WrSpatialId& aSpatialId) { + Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(aASR); + MOZ_ASSERT(spaceAndClip.isSome()); + + CLIP_LOG("Pushing %p override %zu -> %s\n", aASR, spaceAndClip->space.id, + Stringify(aSpatialId.id).c_str()); + + auto it = + mASROverride.insert({spaceAndClip->space, std::stack<wr::WrSpatialId>()}); + it.first->second.push(aSpatialId); + + // Start a new cache + mCacheStack.emplace(); +} + +void ClipManager::PopOverrideForASR(const ActiveScrolledRoot* aASR) { + MOZ_ASSERT(!mCacheStack.empty()); + mCacheStack.pop(); + + Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(aASR); + MOZ_ASSERT(spaceAndClip.isSome()); + + auto it = mASROverride.find(spaceAndClip->space); + CLIP_LOG("Popping %p override %zu -> %s\n", aASR, spaceAndClip->space.id, + Stringify(it->second.top().id).c_str()); + + it->second.pop(); + if (it->second.empty()) { + mASROverride.erase(it); + } +} + +wr::WrSpatialId ClipManager::SpatialIdAfterOverride( + const wr::WrSpatialId& aSpatialId) { + auto it = mASROverride.find(aSpatialId); + if (it == mASROverride.end()) { + return aSpatialId; + } + MOZ_ASSERT(!it->second.empty()); + CLIP_LOG("Overriding %zu with %s\n", aSpatialId.id, + Stringify(it->second.top().id).c_str()); + + return it->second.top(); +} + +wr::WrSpaceAndClipChain ClipManager::SwitchItem(nsDisplayItem* aItem) { + const DisplayItemClipChain* clip = aItem->GetClipChain(); + const ActiveScrolledRoot* asr = aItem->GetActiveScrolledRoot(); + CLIP_LOG("processing item %p (%s) asr %p\n", aItem, + DisplayItemTypeName(aItem->GetType()), asr); + + DisplayItemType type = aItem->GetType(); + if (type == DisplayItemType::TYPE_STICKY_POSITION) { + // For sticky position items, the ASR is computed differently depending + // on whether the item has a fixed descendant or not. But for WebRender + // purposes we always want to use the ASR that would have been used if it + // didn't have fixed descendants, which is stored as the "container ASR" on + // the sticky item. + nsDisplayStickyPosition* sticky = + static_cast<nsDisplayStickyPosition*>(aItem); + asr = sticky->GetContainerASR(); + + // If the leafmost clip for the sticky item is just the displayport clip, + // then skip it. This allows sticky items to remain visible even if the + // rest of the content in the enclosing scrollframe is checkerboarding. + if (sticky->IsClippedToDisplayPort() && clip && clip->mASR == asr) { + clip = clip->mParent; + } + } + + // In most cases we can combine the leaf of the clip chain with the clip rect + // of the display item. This reduces the number of clip items, which avoids + // some overhead further down the pipeline. + bool separateLeaf = false; + if (clip && clip->mASR == asr && clip->mClip.GetRoundedRectCount() == 0) { + // Container display items are not currently supported because the clip + // rect of a stacking context is not handled the same as normal display + // items. + separateLeaf = aItem->GetChildren() == nullptr; + } + + ItemClips clips(asr, clip, separateLeaf); + MOZ_ASSERT(!mItemClipStack.empty()); + if (clips.HasSameInputs(mItemClipStack.top())) { + // Early-exit because if the clips are the same as aItem's previous sibling, + // then we don't need to do do the work of popping the old stuff and then + // pushing it right back on for the new item. Note that if aItem doesn't + // have a previous sibling, that means BeginList would have been called + // just before this, which will have pushed a ItemClips(nullptr, nullptr) + // onto mItemClipStack, so the HasSameInputs check should return false. + CLIP_LOG("\tearly-exit for %p\n", aItem); + return mItemClipStack.top().GetSpaceAndClipChain(); + } + + // Pop aItem's previous sibling's stuff from mBuilder in preparation for + // pushing aItem's stuff. + mItemClipStack.pop(); + + // Zoom display items report their bounds etc using the parent document's + // APD because zoom items act as a conversion layer between the two different + // APDs. + int32_t auPerDevPixel; + if (type == DisplayItemType::TYPE_ZOOM) { + auPerDevPixel = + static_cast<nsDisplayZoom*>(aItem)->GetParentAppUnitsPerDevPixel(); + } else { + auPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); + } + + // If the leaf of the clip chain is going to be merged with the display item's + // clip rect, then we should create a clip chain id from the leaf's parent. + if (separateLeaf) { + CLIP_LOG("\tseparate leaf detected, ignoring the last clip\n"); + clip = clip->mParent; + } + + // There are two ASR chains here that we need to be fully defined. One is the + // ASR chain pointed to by |asr|. The other is the + // ASR chain pointed to by clip->mASR. We pick the leafmost + // of these two chains because that one will include the other. Calling + // DefineScrollLayers with this leafmost ASR will recursively define all the + // ASRs that we care about for this item, but will not actually push + // anything onto the WR stack. + const ActiveScrolledRoot* leafmostASR = asr; + if (clip) { + leafmostASR = ActiveScrolledRoot::PickDescendant(leafmostASR, clip->mASR); + } + Maybe<wr::WrSpaceAndClip> leafmostId = DefineScrollLayers(leafmostASR, aItem); + Unused << leafmostId; + + // Define all the clips in the item's clip chain, and obtain a clip chain id + // for it. + clips.mClipChainId = DefineClipChain(clip, auPerDevPixel); + + Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(asr); + MOZ_ASSERT(spaceAndClip.isSome()); + clips.mScrollId = SpatialIdAfterOverride(spaceAndClip->space); + CLIP_LOG("\tassigning %d -> %d\n", (int)spaceAndClip->space.id, + (int)clips.mScrollId.id); + + // Now that we have the scroll id and a clip id for the item, push it onto + // the WR stack. + clips.UpdateSeparateLeaf(*mBuilder, auPerDevPixel); + auto spaceAndClipChain = clips.GetSpaceAndClipChain(); + mItemClipStack.push(clips); + + CLIP_LOG("done setup for %p\n", aItem); + return spaceAndClipChain; +} + +Maybe<wr::WrSpaceAndClip> ClipManager::GetScrollLayer( + const ActiveScrolledRoot* aASR) { + for (const ActiveScrolledRoot* asr = aASR; asr; asr = asr->mParent) { + Maybe<wr::WrSpaceAndClip> spaceAndClip = + mBuilder->GetScrollIdForDefinedScrollLayer(asr->GetViewId()); + if (spaceAndClip) { + return spaceAndClip; + } + + // If this ASR doesn't have a scroll ID, then we should check its ancestor. + // There may not be one defined because the ASR may not be scrollable or we + // failed to get the scroll metadata. + } + + Maybe<wr::WrSpaceAndClip> spaceAndClip = + mBuilder->GetScrollIdForDefinedScrollLayer( + ScrollableLayerGuid::NULL_SCROLL_ID); + MOZ_ASSERT(spaceAndClip.isSome()); + return spaceAndClip; +} + +Maybe<wr::WrSpaceAndClip> ClipManager::DefineScrollLayers( + const ActiveScrolledRoot* aASR, nsDisplayItem* aItem) { + if (!aASR) { + // Recursion base case + return Nothing(); + } + ScrollableLayerGuid::ViewID viewId = aASR->GetViewId(); + Maybe<wr::WrSpaceAndClip> spaceAndClip = + mBuilder->GetScrollIdForDefinedScrollLayer(viewId); + if (spaceAndClip) { + // If we've already defined this scroll layer before, we can early-exit + return spaceAndClip; + } + // Recurse to define the ancestors + Maybe<wr::WrSpaceAndClip> ancestorSpaceAndClip = + DefineScrollLayers(aASR->mParent, aItem); + + Maybe<ScrollMetadata> metadata = + aASR->mScrollableFrame->ComputeScrollMetadata( + mManager, aItem->ReferenceFrame(), Nothing(), nullptr); + if (!metadata) { + MOZ_ASSERT_UNREACHABLE("Expected scroll metadata to be available!"); + return ancestorSpaceAndClip; + } + + FrameMetrics& metrics = metadata->GetMetrics(); + if (!metrics.IsScrollable()) { + // This item is a scrolling no-op, skip over it in the ASR chain. + return ancestorSpaceAndClip; + } + + nsIScrollableFrame* scrollableFrame = aASR->mScrollableFrame; + nsIFrame* scrollFrame = do_QueryFrame(scrollableFrame); + nsPoint offset = scrollFrame->GetOffsetToCrossDoc(aItem->ReferenceFrame()); + float auPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); + nsRect scrollPort = scrollableFrame->GetScrollPortRect() + offset; + LayoutDeviceRect clipBounds = + LayoutDeviceRect::FromAppUnits(scrollPort, auPerDevPixel); + + // The content rect that we hand to PushScrollLayer should be relative to + // the same origin as the clipBounds that we hand to PushScrollLayer - + // that is, both of them should be relative to the stacking context `aSc`. + // However, when we get the scrollable rect from the FrameMetrics, the + // origin has nothing to do with the position of the frame but instead + // represents the minimum allowed scroll offset of the scrollable content. + // While APZ uses this to clamp the scroll position, we don't need to send + // this to WebRender at all. Instead, we take the position from the + // composition bounds. + LayoutDeviceRect contentRect = + metrics.GetExpandedScrollableRect() * metrics.GetDevPixelsPerCSSPixel(); + contentRect.MoveTo(clipBounds.TopLeft()); + + Maybe<wr::WrSpaceAndClip> parent = ancestorSpaceAndClip; + if (parent) { + parent->space = SpatialIdAfterOverride(parent->space); + } + // The external scroll offset is accumulated into the local space positions of + // display items inside WR, so that the elements hash (intern) to the same + // content ID for quick comparisons. To avoid invalidations when the + // auPerDevPixel is not a round value, round here directly from app units. + // This guarantees we won't introduce any inaccuracy in the external scroll + // offset passed to WR. + LayoutDevicePoint scrollOffset = LayoutDevicePoint::FromAppUnitsRounded( + scrollableFrame->GetScrollPosition(), auPerDevPixel); + + return Some(mBuilder->DefineScrollLayer( + viewId, parent, wr::ToLayoutRect(contentRect), + wr::ToLayoutRect(clipBounds), wr::ToLayoutPoint(scrollOffset))); +} + +Maybe<wr::WrClipChainId> ClipManager::DefineClipChain( + const DisplayItemClipChain* aChain, int32_t aAppUnitsPerDevPixel) { + AutoTArray<wr::WrClipId, 6> clipIds; + // Iterate through the clips in the current item's clip chain, define them + // in WR, and put their IDs into |clipIds|. + for (const DisplayItemClipChain* chain = aChain; chain; + chain = chain->mParent) { + ClipIdMap& cache = mCacheStack.top(); + auto it = cache.find(chain); + if (it != cache.end()) { + // Found it in the currently-active cache, so just use the id we have for + // it. + CLIP_LOG("cache[%p] => %zu\n", chain, it->second.id); + clipIds.AppendElement(it->second); + continue; + } + if (!chain->mClip.HasClip()) { + // This item in the chain is a no-op, skip over it + continue; + } + + LayoutDeviceRect clip = LayoutDeviceRect::FromAppUnits( + chain->mClip.GetClipRect(), aAppUnitsPerDevPixel); + nsTArray<wr::ComplexClipRegion> wrRoundedRects; + chain->mClip.ToComplexClipRegions(aAppUnitsPerDevPixel, wrRoundedRects); + + Maybe<wr::WrSpaceAndClip> spaceAndClip = GetScrollLayer(chain->mASR); + // Before calling DefineClipChain we defined the ASRs by calling + // DefineScrollLayers, so we must have a scrollId here. + MOZ_ASSERT(spaceAndClip.isSome()); + + // Define the clip + spaceAndClip->space = SpatialIdAfterOverride(spaceAndClip->space); + wr::WrClipId clipId = mBuilder->DefineClip( + spaceAndClip, wr::ToLayoutRect(clip), &wrRoundedRects); + clipIds.AppendElement(clipId); + cache[chain] = clipId; + CLIP_LOG("cache[%p] <= %zu\n", chain, clipId.id); + } + + if (clipIds.IsEmpty()) { + return Nothing(); + } + + return Some(mBuilder->DefineClipChain(clipIds)); +} + +ClipManager::~ClipManager() { + MOZ_ASSERT(!mBuilder); + MOZ_ASSERT(mCacheStack.empty()); + MOZ_ASSERT(mItemClipStack.empty()); +} + +ClipManager::ItemClips::ItemClips(const ActiveScrolledRoot* aASR, + const DisplayItemClipChain* aChain, + bool aSeparateLeaf) + : mASR(aASR), mChain(aChain), mSeparateLeaf(aSeparateLeaf) { + mScrollId = wr::wr_root_scroll_node_id(); +} + +void ClipManager::ItemClips::UpdateSeparateLeaf( + wr::DisplayListBuilder& aBuilder, int32_t aAppUnitsPerDevPixel) { + Maybe<wr::LayoutRect> clipLeaf; + if (mSeparateLeaf) { + MOZ_ASSERT(mChain); + clipLeaf.emplace(wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits( + mChain->mClip.GetClipRect(), aAppUnitsPerDevPixel))); + } + + aBuilder.SetClipChainLeaf(clipLeaf); +} + +bool ClipManager::ItemClips::HasSameInputs(const ItemClips& aOther) { + return mASR == aOther.mASR && mChain == aOther.mChain && + mSeparateLeaf == aOther.mSeparateLeaf; +} + +void ClipManager::ItemClips::CopyOutputsFrom(const ItemClips& aOther) { + mScrollId = aOther.mScrollId; + mClipChainId = aOther.mClipChainId; +} + +wr::WrSpaceAndClipChain ClipManager::ItemClips::GetSpaceAndClipChain() const { + auto spaceAndClipChain = wr::RootScrollNodeWithChain(); + spaceAndClipChain.space = mScrollId; + if (mClipChainId) { + spaceAndClipChain.clip_chain = mClipChainId->id; + } + return spaceAndClipChain; +} + +} // namespace layers +} // namespace mozilla |