summaryrefslogtreecommitdiffstats
path: root/gfx/layers/wr/ClipManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/layers/wr/ClipManager.cpp')
-rw-r--r--gfx/layers/wr/ClipManager.cpp424
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