From 0ab5810be5d79dd95efb2a97594646063b75ece1 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Fri, 23 Mar 2018 16:47:37 +1300 Subject: [PATCH] Bug 1443027 - Fix the merging algorithm to pass the new tests correctly. r=mstange MozReview-Commit-ID: JnglCbdhZzE * * * [mq]: update-test MozReview-Commit-ID: JMIzrnVeSTo --HG-- extra : rebase_source : 0ea5ff0e79d1eb1a8f13ea4a17e37fe2601d44e7 --- layout/generic/nsFrame.cpp | 3 +- .../painting/RetainedDisplayListBuilder.cpp | 697 ++++++++---------- layout/painting/RetainedDisplayListBuilder.h | 11 +- layout/painting/RetainedDisplayListHelpers.h | 199 +++++ layout/painting/moz.build | 1 + layout/painting/nsDisplayList.h | 72 +- .../relative/reftest.list | 4 +- .../w3c-css/submitted/ruby/reftest.list | 2 +- 8 files changed, 576 insertions(+), 413 deletions(-) create mode 100644 layout/painting/RetainedDisplayListHelpers.h diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index 49dd19b3e5ad..3b8771f4dfd4 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -3091,12 +3091,11 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder, if (aBuilder->ContainsBlendMode() != BuiltBlendContainer() && aBuilder->IsRetainingDisplayList()) { SetBuiltBlendContainer(aBuilder->ContainsBlendMode()); - aBuilder->MarkCurrentFrameModifiedDuringBuilding(); // If we did a partial build then delete all the items we just built // and repeat building with the full area. if (!aBuilder->GetDirtyRect().Contains(aBuilder->GetVisibleRect())) { - aBuilder->SetDirtyRect(aBuilder->GetVisibleRect()); + aBuilder->MarkCurrentFrameModifiedDuringBuilding(); set.DeleteAll(aBuilder); if (eventRegions) { diff --git a/layout/painting/RetainedDisplayListBuilder.cpp b/layout/painting/RetainedDisplayListBuilder.cpp index ff646a566443..6c888712c09b 100644 --- a/layout/painting/RetainedDisplayListBuilder.cpp +++ b/layout/painting/RetainedDisplayListBuilder.cpp @@ -109,183 +109,75 @@ SelectAGRForFrame(nsIFrame* aFrame, AnimatedGeometryRoot* aParentAGR) // to mark, as child stacking contexts might. It would be nice if we could // jump into those immediately rather than walking the entire thing. bool -RetainedDisplayListBuilder::PreProcessDisplayList(nsDisplayList* aList, +RetainedDisplayListBuilder::PreProcessDisplayList(RetainedDisplayList* aList, AnimatedGeometryRoot* aAGR) { - bool modified = false; + // The DAG merging algorithm does not have strong mechanisms in place to keep the + // complexity of the resulting DAG under control. In some cases we can build up + // edges very quickly. Detect those cases and force a full display list build if + // we hit them. + static const uint32_t kMaxEdgeRatio = 5; + bool initializeDAG = !aList->mDAG.Length(); + if (!initializeDAG && + aList->mDAG.mDirectPredecessorList.Length() > + (aList->mDAG.mNodesInfo.Length() * kMaxEdgeRatio)) { + return false; + + } + nsDisplayList saved; - while (nsDisplayItem* i = aList->RemoveBottom()) { - if (i->HasDeletedFrame() || !i->CanBeReused()) { - i->Destroy(&mBuilder); - modified = true; + aList->mOldItems.SetCapacity(aList->Count()); + size_t i = 0; + while (nsDisplayItem* item = aList->RemoveBottom()) { + if (item->HasDeletedFrame() || !item->CanBeReused()) { + // If we haven't yet initialized the DAG, then we can + // just drop this item. Otherwise we need to keep it + // around to preserve indices, and merging will + // get rid of it. + if (initializeDAG) { + item->Destroy(&mBuilder); + } else { + aList->mOldItems.AppendElement(OldItemInfo(item)); + } continue; } - nsIFrame* f = i->Frame(); + aList->mOldItems.AppendElement(OldItemInfo(item)); + if (initializeDAG) { + if (i == 0) { + aList->mDAG.AddNode(Span()); + } else { + MergedListIndex previous(i - 1); + aList->mDAG.AddNode(Span(&previous, 1)); + } - if (i->GetChildren()) { - if (PreProcessDisplayList(i->GetChildren(), SelectAGRForFrame(f, aAGR))) { - modified = true; + aList->mKeyLookup.Put({ item->Frame(), item->GetPerFrameKey() }, i); + i++; + } + + nsIFrame* f = item->Frame(); + + if (item->GetChildren()) { + if (!PreProcessDisplayList(item->GetChildren(), SelectAGRForFrame(f, aAGR))) { + mBuilder.MarkFrameForDisplayIfVisible(f, mBuilder.RootReferenceFrame()); + mBuilder.MarkFrameModifiedDuringBuilding(f); } } // TODO: We should be able to check the clipped bounds relative // to the common AGR (of both the existing item and the invalidated // frame) and determine if they can ever intersect. - if (aAGR && i->GetAnimatedGeometryRoot()->GetAsyncAGR() != aAGR) { + if (aAGR && item->GetAnimatedGeometryRoot()->GetAsyncAGR() != aAGR) { mBuilder.MarkFrameForDisplayIfVisible(f, mBuilder.RootReferenceFrame()); - modified = true; } // TODO: This is here because we sometimes reuse the previous display list // completely. For optimization, we could only restore the state for reused // display items. - i->RestoreState(); - - saved.AppendToTop(i); + item->RestoreState(); } - aList->AppendToTop(&saved); aList->RestoreState(); - return modified; -} - -struct DisplayItemKey -{ - bool operator ==(const DisplayItemKey& aOther) const { - return mFrame == aOther.mFrame && - mPerFrameKey == aOther.mPerFrameKey; - } - - nsIFrame* mFrame; - uint32_t mPerFrameKey; -}; - -class DisplayItemHashEntry : public PLDHashEntryHdr -{ -public: - typedef DisplayItemKey KeyType; - typedef const DisplayItemKey* KeyTypePointer; - - explicit DisplayItemHashEntry(KeyTypePointer aKey) - : mKey(*aKey) {} - explicit DisplayItemHashEntry(const DisplayItemHashEntry& aCopy)=default; - - ~DisplayItemHashEntry() = default; - - KeyType GetKey() const { return mKey; } - bool KeyEquals(KeyTypePointer aKey) const - { - return mKey == *aKey; - } - - static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; } - static PLDHashNumber HashKey(KeyTypePointer aKey) - { - if (!aKey) - return 0; - - return mozilla::HashGeneric(aKey->mFrame, aKey->mPerFrameKey); - } - enum { ALLOW_MEMMOVE = true }; - - DisplayItemKey mKey; -}; - -template -void SwapAndRemove(nsTArray& aArray, uint32_t aIndex) -{ - if (aIndex != (aArray.Length() - 1)) { - T last = aArray.LastElement(); - aArray.LastElement() = aArray[aIndex]; - aArray[aIndex] = last; - } - - aArray.RemoveLastElement(); -} - -static bool -MergeFrameRects(nsDisplayLayerEventRegions* aOldItem, - nsDisplayLayerEventRegions* aNewItem, - nsDisplayLayerEventRegions::FrameRects nsDisplayLayerEventRegions::*aRectList, - nsTArray& aAddedFrames) -{ - bool modified = false; - // Go through the old item's rect list and remove any rectangles - // belonging to invalidated frames (deleted frames should - // already be gone at this point) - nsDisplayLayerEventRegions::FrameRects& oldRects = aOldItem->*aRectList; - uint32_t i = 0; - while (i < oldRects.mFrames.Length()) { - // TODO: As mentioned in nsDisplayLayerEventRegions, this - // operation might perform really poorly on a vector. - nsIFrame* f = oldRects.mFrames[i]; - if (IsAnyAncestorModified(f)) { - MOZ_ASSERT(f != aOldItem->Frame()); - f->RemoveDisplayItem(aOldItem); - SwapAndRemove(oldRects.mFrames, i); - SwapAndRemove(oldRects.mBoxes, i); - modified = true; - } else { - i++; - } - } - if (!aNewItem) { - return modified; - } - - // Copy items from the source list to the dest list, but - // only if the dest doesn't already include them. - nsDisplayItem* destItem = aOldItem; - nsDisplayLayerEventRegions::FrameRects* destRects = &(aOldItem->*aRectList); - nsDisplayLayerEventRegions::FrameRects* srcRects = &(aNewItem->*aRectList); - - for (uint32_t i = 0; i < srcRects->mFrames.Length(); i++) { - nsIFrame* f = srcRects->mFrames[i]; - if (!f->HasDisplayItem(destItem)) { - // If this frame isn't already in the destination item, - // then add it! - destRects->Add(f, srcRects->mBoxes[i]); - - // We also need to update RealDisplayItemData for 'f', - // but that'll mess up this check for the following - // FrameRects lists, so defer that until the end. - aAddedFrames.AppendElement(f); - MOZ_ASSERT(f != aOldItem->Frame()); - - modified = true; - } - - } - return modified; -} - -bool MergeLayerEventRegions(nsDisplayItem* aOldItem, - nsDisplayItem* aNewItem) -{ - nsDisplayLayerEventRegions* oldItem = - static_cast(aOldItem); - nsDisplayLayerEventRegions* newItem = - static_cast(aNewItem); - - nsTArray addedFrames; - - bool modified = false; - modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mHitRegion, addedFrames); - modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mMaybeHitRegion, addedFrames); - modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mDispatchToContentHitRegion, addedFrames); - modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mNoActionRegion, addedFrames); - modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mHorizontalPanRegion, addedFrames); - modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mVerticalPanRegion, addedFrames); - - // MergeFrameRects deferred updating the display item data list during - // processing so that earlier calls didn't change the result of later - // ones. Fix that up now. - for (nsIFrame* f : addedFrames) { - if (!f->HasDisplayItem(aOldItem)) { - f->AddDisplayItem(aOldItem); - } - } - return modified; + return true; } void @@ -322,266 +214,280 @@ UpdateASR(nsDisplayItem* aItem, aContainerASR.value())); } -/** - * Takes two display lists and merges them into an output list. - * - * The basic algorithm is: - * - * For-each item i in the new list: - * If the item has a matching item in the old list: - * Remove items from the start of the old list up until we reach an item that also exists in the new list (leaving the matched item in place): - * Add valid items to the merged list, destroy invalid items. - * Add i into the merged list. - * If the start of the old list matches i, remove and destroy it, otherwise mark the old version of i as used. - * Add all remaining valid items from the old list into the merged list, skipping over (and destroying) any that are marked as used. - * - * If any item has a child display list, then we recurse into the merge - * algorithm once we match up the new/old versions (if present). - * - * Example 1: - * - * Old List: A,B,C,D - * Modified List: A,D - * Invalidations: C,D - * - * We first match the A items, and add the new one to the merged list. - * We then match the D items, copy B into the merged list, but not C - * (since it's invalid). We then add the new D to the list and we're - * finished. - * - * Merged List: A,B,D - * - * Example 2 (layout/reftests/retained-dl-zindex-1.html): - * - * Old List: A, B - * Modified List: B, A - * Invalidations: A - * - * In this example A has been explicitly moved to the back. - * - * We match the B items, but don't copy A since it's invalid, and then add the - * new B into the merged list. We then add A, and we're done. - * - * Merged List: B, A - * - * Example 3: - * - * Old List: A, B - * Modified List: B, A - * Invalidations: - - * - * This can happen because a prior merge might have changed the ordering - * for non-intersecting items. - * - * We match the B items, but don't copy A since it's also present in the new list - * and then add the new B into the merged list. We then add A, and we're done. - * - * Merged List: B, A - * - * Example 4 (layout/reftests/retained-dl-zindex-2.html): - * - * Element A has two elements covering it (B and C), that don't intersect each - * other. We then move C to the back. - * - * The correct initial ordering has B and C after A, in any order. - * - * Old List: A, B, C - * Modified List: C, A - * Invalidations: C - * - * We match the C items, but don't add anything from the old list because A is present - * in both lists. We add C to the merged list, and mark the old version of C as reused. - * - * We then match A, add the new version the merged list and delete the old version. - * - * We then process the remainder of the old list, B is added (since it is valid, - * and hasn't been mark as reused), C is destroyed since it's marked as reused and - * is already present in the merged list. - * - * Merged List: C, A, B - */ -bool -RetainedDisplayListBuilder::MergeDisplayLists(nsDisplayList* aNewList, - nsDisplayList* aOldList, - nsDisplayList* aOutList, - Maybe& aOutContainerASR) +void +OldItemInfo::AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder, + MergedListIndex aIndex) { - bool modified = false; + mItem->Destroy(aBuilder->Builder()); + AddedToMergedList(aIndex); +} - nsDisplayList merged; - const auto UseItem = [&](nsDisplayItem* aItem) { +void +OldItemInfo::Discard(RetainedDisplayListBuilder* aBuilder, + nsTArray&& aDirectPredecessors) +{ + MOZ_ASSERT(!IsUsed()); + mUsed = mDiscarded = true; + mDirectPredecessors = Move(aDirectPredecessors); + mItem->Destroy(aBuilder->Builder()); + mItem = nullptr; +} + +/** + * A C++ implementation of Markus Stange's merge-dags algorthim. + * https://github.com/mstange/merge-dags + * + * MergeState handles combining a new list of display items into an existing + * DAG and computes the new DAG in a single pass. + * Each time we add a new item, we resolve all dependencies for it, so that the resulting + * list and DAG are built in topological ordering. + */ +class MergeState { +public: + MergeState(RetainedDisplayListBuilder* aBuilder, RetainedDisplayList&& aOldList) + : mBuilder(aBuilder) + , mOldItems(Move(aOldList.mOldItems)) + , mOldDAG(Move(*reinterpret_cast*>(&aOldList.mDAG))) + , mResultIsModified(false) + { + mOldKeyLookup.SwapElements(aOldList.mKeyLookup); + mMergedDAG.EnsureCapacityFor(mOldDAG); + } + + MergedListIndex ProcessItemFromNewList(nsDisplayItem* aNewItem, const Maybe& aPreviousItem) { + OldListIndex oldIndex; + if (mOldKeyLookup.Get({ aNewItem->Frame(), aNewItem->GetPerFrameKey() }, &oldIndex.val)) { + bool changed = IsChanged(aNewItem); + if (!changed || HasSameSinglePredecessor(oldIndex, aPreviousItem)) { + MOZ_ASSERT(!mOldItems[oldIndex.val].IsUsed()); + if (changed) { + mResultIsModified = true; + } else if (aNewItem->GetChildren()) { + Maybe containerASRForChildren; + if (mBuilder->MergeDisplayLists(aNewItem->GetChildren(), + mOldItems[oldIndex.val].mItem->GetChildren(), + aNewItem->GetChildren(), + containerASRForChildren)) { + mResultIsModified = true; + + } + UpdateASR(aNewItem, containerASRForChildren); + aNewItem->UpdateBounds(mBuilder->Builder()); + } + + AutoTArray directPredecessors = ProcessPredecessorsOfOldNode(oldIndex); + MergedListIndex newIndex = AddNewNode(aNewItem, Some(oldIndex), directPredecessors, aPreviousItem); + mOldItems[oldIndex.val].AddedMatchToMergedList(mBuilder, newIndex); + return newIndex; + } + } + mResultIsModified = true; + return AddNewNode(aNewItem, Nothing(), Span(), aPreviousItem); + } + + bool HasSameSinglePredecessor(OldListIndex aNode, + const Maybe& aNewItemPredecessor) + { + if (!aNewItemPredecessor) { + return false; + } + + Span directPredecessors = mOldDAG.GetDirectPredecessors(aNode); + if (directPredecessors.Length() != 1) { + return false; + } + + OldItemInfo& oldItem = mOldItems[directPredecessors[0].val]; + if (oldItem.IsUsed() && !oldItem.IsDiscarded()) { + return oldItem.mIndex == aNewItemPredecessor.value(); + } + return false; + } + + RetainedDisplayList Finalize() { + for (size_t i = 0; i < mOldDAG.Length(); i++) { + if (mOldItems[i].IsUsed()) { + continue; + } + + AutoTArray directPredecessors = + ResolveNodeIndexesOldToMerged(mOldDAG.GetDirectPredecessors(OldListIndex(i))); + ProcessOldNode(OldListIndex(i), Move(directPredecessors)); + } + + RetainedDisplayList result; + result.AppendToTop(&mMergedItems); + result.mDAG = Move(mMergedDAG); + result.mKeyLookup.SwapElements(mMergedKeyLookup); + return result; + } + + bool IsChanged(nsDisplayItem* aItem) { + return aItem->HasDeletedFrame() || !aItem->CanBeReused() || + IsAnyAncestorModified(aItem->FrameForInvalidation()); + } + + void UpdateContainerASR(nsDisplayItem* aItem) + { const ActiveScrolledRoot* itemClipASR = aItem->GetClipChain() ? aItem->GetClipChain()->mASR : nullptr; const ActiveScrolledRoot* finiteBoundsASR = ActiveScrolledRoot::PickDescendant( itemClipASR, aItem->GetActiveScrolledRoot()); - if (!aOutContainerASR) { - aOutContainerASR = Some(finiteBoundsASR); + if (!mContainerASR) { + mContainerASR = Some(finiteBoundsASR); } else { - aOutContainerASR = - Some(ActiveScrolledRoot::PickAncestor(aOutContainerASR.value(), finiteBoundsASR)); + mContainerASR = Some(ActiveScrolledRoot::PickAncestor(mContainerASR.value(), finiteBoundsASR)); } - merged.AppendToTop(aItem); - }; + } - const auto ReuseItem = [&](nsDisplayItem* aItem) { - UseItem(aItem); - aItem->SetReused(true); + MergedListIndex AddNewNode(nsDisplayItem* aItem, + const Maybe& aOldIndex, + Span aDirectPredecessors, + const Maybe& aExtraDirectPredecessor) { + UpdateContainerASR(aItem); + mMergedItems.AppendToTop(aItem); + MergedListIndex newIndex = mMergedDAG.AddNode(aDirectPredecessors, aExtraDirectPredecessor); + mMergedKeyLookup.Put({ aItem->Frame(), aItem->GetPerFrameKey() }, newIndex.val); + return newIndex; + } - if (aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) { - IncrementSubDocPresShellPaintCount(aItem); - } - }; - - const bool newListIsEmpty = aNewList->IsEmpty(); - if (!newListIsEmpty) { - // Build a hashtable of items in the old list so we can look for them quickly. - // We have similar data in the nsIFrame DisplayItems() property, but it doesn't - // know which display list items are in, and we only want to match items in - // this list. - nsDataHashtable oldListLookup(aOldList->Count()); - - for (nsDisplayItem* i = aOldList->GetBottom(); i != nullptr; i = i->GetAbove()) { - i->SetReused(false); - oldListLookup.Put({ i->Frame(), i->GetPerFrameKey() }, i); - } - - nsDataHashtable newListLookup(aNewList->Count()); - for (nsDisplayItem* i = aNewList->GetBottom(); i != nullptr; i = i->GetAbove()) { -#ifdef DEBUG - if (newListLookup.Get({ i->Frame(), i->GetPerFrameKey() }, nullptr)) { - MOZ_CRASH_UNSAFE_PRINTF("Duplicate display items detected!: %s(0x%p) type=%d key=%d", - i->Name(), i->Frame(), - static_cast(i->GetType()), i->GetPerFrameKey()); + void ProcessOldNode(OldListIndex aNode, nsTArray&& aDirectPredecessors) { + nsDisplayItem* item = mOldItems[aNode.val].mItem; + if (IsChanged(item)) { + mOldItems[aNode.val].Discard(mBuilder, Move(aDirectPredecessors)); + mResultIsModified = true; + } else { + if (item->GetChildren()) { + Maybe containerASRForChildren; + nsDisplayList empty; + if (mBuilder->MergeDisplayLists(&empty, item->GetChildren(), item->GetChildren(), + containerASRForChildren)) { + mResultIsModified = true; + } + UpdateASR(item, containerASRForChildren); + item->UpdateBounds(mBuilder->Builder()); } -#endif - newListLookup.Put({ i->Frame(), i->GetPerFrameKey() }, i); + if (item->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) { + mBuilder->IncrementSubDocPresShellPaintCount(item); + } + item->SetReused(true); + mOldItems[aNode.val].AddedToMergedList( + AddNewNode(item, Some(aNode), aDirectPredecessors, Nothing())); + } + } + + struct PredecessorStackItem { + PredecessorStackItem(OldListIndex aNode, Span aPredecessors) + : mNode(aNode) + , mDirectPredecessors(aPredecessors) + , mCurrentPredecessorIndex(0) + {} + + bool IsFinished() { + return mCurrentPredecessorIndex == mDirectPredecessors.Length(); } - while (nsDisplayItem* newItem = aNewList->RemoveBottom()) { - if (nsDisplayItem* oldItem = oldListLookup.Get({ newItem->Frame(), newItem->GetPerFrameKey() })) { - // The new item has a matching counterpart in the old list that we haven't yet reached, - // so copy all valid items from the old list into the merged list until we get to the - // matched item. - nsDisplayItem* old = nullptr; - while ((old = aOldList->GetBottom()) && old != oldItem) { - if (IsAnyAncestorModified(old->FrameForInvalidation())) { - // The old item is invalid, discard it. - oldListLookup.Remove({ old->Frame(), old->GetPerFrameKey() }); - aOldList->RemoveBottom(); - old->Destroy(&mBuilder); - modified = true; - } else if (newListLookup.Get({ old->Frame(), old->GetPerFrameKey() })) { - // This old item is also in the new list, but we haven't got to it yet. - // Stop now, and we'll deal with it when we get to the new entry. - modified = true; - break; - } else { - // Recurse into the child list (without a matching new list) to - // ensure that we find and remove any invalidated items. - if (old->GetChildren()) { - nsDisplayList empty; - Maybe containerASRForChildren; - if (MergeDisplayLists(&empty, old->GetChildren(), - old->GetChildren(), containerASRForChildren)) { - modified = true; - } - UpdateASR(old, containerASRForChildren); - old->UpdateBounds(&mBuilder); - } - aOldList->RemoveBottom(); - ReuseItem(old); - } - } - bool destroy = false; - if (old == oldItem) { - // If we advanced the old list until the matching item then we can pop - // the matching item off the old list and make sure we clean it up. - aOldList->RemoveBottom(); - destroy = true; - } else { - // If we didn't get to the matching item, then mark the old item - // as being reused (since we're adding the new version to the new - // list now) so that we don't add it twice at the end. - oldItem->SetReused(true); - } + OldListIndex GetAndIncrementCurrentPredecessor() { return mDirectPredecessors[mCurrentPredecessorIndex++]; } - // Recursively merge any child lists, destroy the old item and add - // the new one to the list. - if (destroy && - oldItem->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS && - !IsAnyAncestorModified(oldItem->FrameForInvalidation())) { - // Event regions items don't have anything interesting other than - // the lists of regions and frames, so we have no need to use the - // newer item. Always use the old item instead since we assume it's - // likely to have the bigger lists and merging will be quicker. - if (MergeLayerEventRegions(oldItem, newItem)) { - modified = true; - } - ReuseItem(oldItem); - newItem->Destroy(&mBuilder); - } else { - if (IsAnyAncestorModified(oldItem->FrameForInvalidation())) { - modified = true; - } else if (oldItem->GetChildren()) { - MOZ_ASSERT(newItem->GetChildren()); - Maybe containerASRForChildren; - if (MergeDisplayLists(newItem->GetChildren(), oldItem->GetChildren(), - newItem->GetChildren(), containerASRForChildren)) { - modified = true; - } - UpdateASR(newItem, containerASRForChildren); - newItem->UpdateBounds(&mBuilder); - } + OldListIndex mNode; + Span mDirectPredecessors; + size_t mCurrentPredecessorIndex; + }; - if (destroy) { - oldItem->Destroy(&mBuilder); - } - UseItem(newItem); + AutoTArray ProcessPredecessorsOfOldNode(OldListIndex aNode) { + AutoTArray mStack; + mStack.AppendElement(PredecessorStackItem(aNode, mOldDAG.GetDirectPredecessors(aNode))); + + while (true) { + if (mStack.LastElement().IsFinished()) { + // If we've finished processing all the entries in the current set, then pop + // it off the processing stack and process it. + PredecessorStackItem item = mStack.PopLastElement(); + AutoTArray result = + ResolveNodeIndexesOldToMerged(item.mDirectPredecessors); + if (mStack.IsEmpty()) { + return result; + } else { + ProcessOldNode(item.mNode, Move(result)); } } else { - // If there was no matching item in the old list, then we only need to - // add the new item to the merged list. - modified = true; - UseItem(newItem); + // Grab the current predecessor, push predecessors of that onto the processing + // stack (if it hasn't already been processed), and then advance to the next entry. + OldListIndex currentIndex = mStack.LastElement().GetAndIncrementCurrentPredecessor(); + if (!mOldItems[currentIndex.val].IsUsed()) { + mStack.AppendElement( + PredecessorStackItem(currentIndex, mOldDAG.GetDirectPredecessors(currentIndex))); + } } } } - // Reuse the remaining valid items from the old display list. - while (nsDisplayItem* old = aOldList->RemoveBottom()) { - if (!IsAnyAncestorModified(old->FrameForInvalidation()) && - (!old->IsReused() || newListIsEmpty)) { - if (old->GetChildren()) { - // We are calling MergeDisplayLists() to ensure that the display items - // with modified or deleted children will be correctly handled. - // Passing an empty new display list as an argument skips the merging - // loop above and jumps back here. - nsDisplayList empty; - Maybe containerASRForChildren; - - if (MergeDisplayLists(&empty, old->GetChildren(), - old->GetChildren(), containerASRForChildren)) { - modified = true; + AutoTArray ResolveNodeIndexesOldToMerged(Span aDirectPredecessors) { + AutoTArray result; + result.SetCapacity(aDirectPredecessors.Length()); + for (OldListIndex index : aDirectPredecessors) { + OldItemInfo& oldItem = mOldItems[index.val]; + if (oldItem.IsDiscarded()) { + for (MergedListIndex inner : oldItem.mDirectPredecessors) { + if (!result.Contains(inner)) { + result.AppendElement(inner); + } } - UpdateASR(old, containerASRForChildren); - old->UpdateBounds(&mBuilder); + } else { + result.AppendElement(oldItem.mIndex); } - if (old->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) { - if (MergeLayerEventRegions(old, nullptr)) { - modified = true; - } - } - ReuseItem(old); - } else { - old->Destroy(&mBuilder); - modified = true; } + return result; } - aOutList->AppendToTop(&merged); - return modified; + RetainedDisplayListBuilder* mBuilder; + Maybe mContainerASR; + nsTArray mOldItems; + DirectedAcyclicGraph mOldDAG; + // Unfortunately we can't use strong typing for the hashtables + // since they internally encode the type with the mOps pointer, + // and assert when we try swap the contents + nsDataHashtable mOldKeyLookup; + nsDisplayList mMergedItems; + DirectedAcyclicGraph mMergedDAG; + nsDataHashtable mMergedKeyLookup; + bool mResultIsModified; +}; + +void RetainedDisplayList::ClearDAG() +{ + mDAG.Clear(); + mKeyLookup.Clear(); +} + +/** + * Takes two display lists and merges them into an output list. + * + * Display lists wthout an explicit DAG are interpreted as linear DAGs (with a maximum + * of one direct predecessor and one direct successor per node). We add the two DAGs + * together, and then output the topological sorted ordering as the final display list. + * + * Once we've merged a list, we then retain the DAG (as part of the RetainedDisplayList + * object) to use for future merges. + */ +bool +RetainedDisplayListBuilder::MergeDisplayLists(nsDisplayList* aNewList, + RetainedDisplayList* aOldList, + RetainedDisplayList* aOutList, + mozilla::Maybe& aOutContainerASR) +{ + MergeState merge(this, Move(*aOldList)); + + Maybe previousItemIndex; + while (nsDisplayItem* item = aNewList->RemoveBottom()) { + previousItemIndex = Some(merge.ProcessItemFromNewList(item, previousItemIndex)); + } + + *aOutList = Move(merge.Finalize()); + aOutContainerASR = merge.mContainerASR; + return merge.mResultIsModified; } static void @@ -1095,7 +1001,7 @@ RetainedDisplayListBuilder::AttemptPartialUpdate( mBuilder.ClearWindowOpaqueRegion(); if (mBuilder.ShouldSyncDecodeImages()) { - MarkFramesWithItemsAndImagesModified(&mList); + MarkFramesWithItemsAndImagesModified(List()); } mBuilder.EnterPresShell(mBuilder.RootReferenceFrame()); @@ -1109,7 +1015,7 @@ RetainedDisplayListBuilder::AttemptPartialUpdate( // Do not allow partial builds if the retained display list is empty, or if // ShouldBuildPartial heuristic fails. - const bool shouldBuildPartial = !mList.IsEmpty() && ShouldBuildPartial(modifiedFrames.Frames()); + const bool shouldBuildPartial = !List()->IsEmpty() && ShouldBuildPartial(modifiedFrames.Frames()); if (mPreviousCaret != mBuilder.GetCaretFrame()) { if (mPreviousCaret) { @@ -1131,16 +1037,17 @@ RetainedDisplayListBuilder::AttemptPartialUpdate( AnimatedGeometryRoot* modifiedAGR = nullptr; if (!shouldBuildPartial || !ComputeRebuildRegion(modifiedFrames.Frames(), &modifiedDirty, - &modifiedAGR, framesWithProps.Frames())) { - mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), &mList); + &modifiedAGR, framesWithProps.Frames()) || + !PreProcessDisplayList(&mList, modifiedAGR)) { + mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), List()); + mList.ClearDAG(); return PartialUpdateResult::Failed; } modifiedDirty.IntersectRect(modifiedDirty, mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf()); PartialUpdateResult result = PartialUpdateResult::NoChange; - if (PreProcessDisplayList(&mList, modifiedAGR) || - !modifiedDirty.IsEmpty() || + if (!modifiedDirty.IsEmpty() || !framesWithProps.IsEmpty()) { result = PartialUpdateResult::Updated; } @@ -1183,6 +1090,6 @@ RetainedDisplayListBuilder::AttemptPartialUpdate( //printf_stderr("Painting --- Merged list:\n"); //nsFrame::PrintDisplayList(&mBuilder, mList); - mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), &mList); + mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), List()); return result; } diff --git a/layout/painting/RetainedDisplayListBuilder.h b/layout/painting/RetainedDisplayListBuilder.h index 82719f634c9a..18aad41097a7 100644 --- a/layout/painting/RetainedDisplayListBuilder.h +++ b/layout/painting/RetainedDisplayListBuilder.h @@ -49,11 +49,10 @@ struct RetainedDisplayListBuilder { NS_DECLARE_FRAME_PROPERTY_DELETABLE(Cached, RetainedDisplayListBuilder) private: - bool PreProcessDisplayList(nsDisplayList* aList, AnimatedGeometryRoot* aAGR); - + bool PreProcessDisplayList(RetainedDisplayList* aList, AnimatedGeometryRoot* aAGR); bool MergeDisplayLists(nsDisplayList* aNewList, - nsDisplayList* aOldList, - nsDisplayList* aOutList, + RetainedDisplayList* aOldList, + RetainedDisplayList* aOutList, mozilla::Maybe& aOutContainerASR); bool ComputeRebuildRegion(nsTArray& aModifiedFrames, @@ -63,8 +62,10 @@ private: void IncrementSubDocPresShellPaintCount(nsDisplayItem* aItem); + friend class MergeState; + nsDisplayListBuilder mBuilder; - nsDisplayList mList; + RetainedDisplayList mList; WeakFrame mPreviousCaret; }; diff --git a/layout/painting/RetainedDisplayListHelpers.h b/layout/painting/RetainedDisplayListHelpers.h new file mode 100644 index 000000000000..297ab6be3a51 --- /dev/null +++ b/layout/painting/RetainedDisplayListHelpers.h @@ -0,0 +1,199 @@ +/* -*- 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/. */ + +#ifndef RETAINEDDISPLAYLISTHELPERS_H_ +#define RETAINEDDISPLAYLISTHELPERS_H_ + +#include "PLDHashTable.h" + +struct DisplayItemKey +{ + bool operator ==(const DisplayItemKey& aOther) const { + return mFrame == aOther.mFrame && + mPerFrameKey == aOther.mPerFrameKey; + } + + nsIFrame* mFrame; + uint32_t mPerFrameKey; +}; + +class DisplayItemHashEntry : public PLDHashEntryHdr +{ +public: + typedef DisplayItemKey KeyType; + typedef const DisplayItemKey* KeyTypePointer; + + explicit DisplayItemHashEntry(KeyTypePointer aKey) + : mKey(*aKey) {} + explicit DisplayItemHashEntry(const DisplayItemHashEntry& aCopy)=default; + + ~DisplayItemHashEntry() = default; + + KeyType GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const + { + return mKey == *aKey; + } + + static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) + { + if (!aKey) + return 0; + + return mozilla::HashGeneric(aKey->mFrame, aKey->mPerFrameKey); + } + enum { ALLOW_MEMMOVE = true }; + + DisplayItemKey mKey; +}; + +template +bool SpanContains(mozilla::Span& aSpan, T aItem) +{ + for (const T& i : aSpan) { + if (i == aItem) { + return true; + } + } + return false; +} + +class OldListUnits {}; +class MergedListUnits {}; + +template +struct Index { + Index() + : val(0) + {} + explicit Index(size_t aVal) + : val(aVal) + {} + + bool operator==(const Index& aOther) const + { + return val == aOther.val; + } + + size_t val; +}; +typedef Index OldListIndex; +typedef Index MergedListIndex; + + +template +class DirectedAcyclicGraph { +public: + DirectedAcyclicGraph() {} + DirectedAcyclicGraph(DirectedAcyclicGraph&& aOther) + : mNodesInfo(mozilla::Move(aOther.mNodesInfo)) + , mDirectPredecessorList(mozilla::Move(aOther.mDirectPredecessorList)) + {} + + DirectedAcyclicGraph& operator=(DirectedAcyclicGraph&& aOther) + { + mNodesInfo = mozilla::Move(aOther.mNodesInfo); + mDirectPredecessorList = mozilla::Move(aOther.mDirectPredecessorList); + return *this; + } + + Index AddNode(mozilla::Span> aDirectPredecessors, + const mozilla::Maybe>& aExtraPredecessor = mozilla::Nothing()) + { + size_t index = mNodesInfo.Length(); + mNodesInfo.AppendElement(NodeInfo(mDirectPredecessorList.Length(), aDirectPredecessors.Length())); + if (aExtraPredecessor && !SpanContains(aDirectPredecessors, aExtraPredecessor.value())) { + mNodesInfo.LastElement().mDirectPredecessorCount++; + mDirectPredecessorList.SetCapacity(mDirectPredecessorList.Length() + aDirectPredecessors.Length() + 1); + mDirectPredecessorList.AppendElements(aDirectPredecessors); + mDirectPredecessorList.AppendElement(aExtraPredecessor.value()); + } else { + mDirectPredecessorList.AppendElements(aDirectPredecessors); + } + return Index(index); + } + + size_t Length() + { + return mNodesInfo.Length(); + } + + mozilla::Span> GetDirectPredecessors(Index aNodeIndex) + { + NodeInfo& node = mNodesInfo[aNodeIndex.val]; + return mozilla::MakeSpan(mDirectPredecessorList).Subspan(node.mIndexInDirectPredecessorList, + node.mDirectPredecessorCount); + } + + template + void EnsureCapacityFor(const DirectedAcyclicGraph& aOther) + { + mNodesInfo.SetCapacity(aOther.mNodesInfo.Length()); + mDirectPredecessorList.SetCapacity(aOther.mDirectPredecessorList.Length()); + } + + void Clear() + { + mNodesInfo.Clear(); + mDirectPredecessorList.Clear(); + } + + struct NodeInfo { + NodeInfo(size_t aIndexInDirectPredecessorList, + size_t aDirectPredecessorCount) + : mIndexInDirectPredecessorList(aIndexInDirectPredecessorList) + , mDirectPredecessorCount(aDirectPredecessorCount) + {} + size_t mIndexInDirectPredecessorList; + size_t mDirectPredecessorCount; + }; + + nsTArray mNodesInfo; + nsTArray> mDirectPredecessorList; +}; + +struct RetainedDisplayListBuilder; +class nsDisplayItem; + +struct OldItemInfo { + explicit OldItemInfo(nsDisplayItem* aItem) + : mItem(aItem) + , mUsed(false) + , mDiscarded(false) + {} + + void AddedToMergedList(MergedListIndex aIndex) + { + MOZ_ASSERT(!IsUsed()); + mUsed = true; + mIndex = aIndex; + mItem = nullptr; + } + + void AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder, + MergedListIndex aIndex); + void Discard(RetainedDisplayListBuilder* aBuilder, + nsTArray&& aDirectPredecessors); + bool IsUsed() + { + return mUsed; + } + + bool IsDiscarded() + { + MOZ_ASSERT(IsUsed()); + return mDiscarded; + } + + nsDisplayItem* mItem; + bool mUsed; + bool mDiscarded; + MergedListIndex mIndex; + nsTArray mDirectPredecessors; +}; + +#endif // RETAINEDDISPLAYLISTHELPERS_H_ diff --git a/layout/painting/moz.build b/layout/painting/moz.build index bbcb8519686f..24c694176784 100644 --- a/layout/painting/moz.build +++ b/layout/painting/moz.build @@ -22,6 +22,7 @@ EXPORTS += [ 'nsDisplayListInvalidation.h', 'nsImageRenderer.h', 'RetainedDisplayListBuilder.h', + 'RetainedDisplayListHelpers.h', ] EXPORTS.mozilla += [ diff --git a/layout/painting/nsDisplayList.h b/layout/painting/nsDisplayList.h index 8fce01436ec8..5d080e4b6370 100644 --- a/layout/painting/nsDisplayList.h +++ b/layout/painting/nsDisplayList.h @@ -44,6 +44,7 @@ #include "nsPresArena.h" #include "nsAutoLayoutPhase.h" #include "nsDisplayItemTypes.h" +#include "RetainedDisplayListHelpers.h" #include #include "nsTHashtable.h" @@ -1093,11 +1094,11 @@ public: aBuilder->mCurrentAGR = aBuilder->FindAnimatedGeometryRootFor(aForChild); } MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(aBuilder->RootReferenceFrame(), *aBuilder->mCurrentAGR)); + aBuilder->mInInvalidSubtree = aBuilder->mInInvalidSubtree || aForChild->IsFrameModified(); aBuilder->mCurrentFrame = aForChild; aBuilder->mVisibleRect = aVisibleRect; - aBuilder->mDirtyRect = aDirtyRect; + aBuilder->mDirtyRect = aBuilder->mInInvalidSubtree ? aVisibleRect : aDirtyRect; aBuilder->mIsAtRootOfPseudoStackingContext = aIsRoot; - aBuilder->mInInvalidSubtree = aBuilder->mInInvalidSubtree || aForChild->IsFrameModified(); } void SetReferenceFrameAndCurrentOffset(const nsIFrame* aFrame, const nsPoint& aOffset) { mBuilder->mCurrentReferenceFrame = aFrame; @@ -1737,6 +1738,7 @@ public: { if (MarkFrameModifiedDuringBuilding(const_cast(mCurrentFrame))) { mInInvalidSubtree = true; + mDirtyRect = mVisibleRect; return true; } return false; @@ -2012,6 +2014,7 @@ private: class nsDisplayItem; class nsDisplayList; +class RetainedDisplayList; /** * nsDisplayItems are put in singly-linked lists rooted in an nsDisplayList. * nsDisplayItemLink holds the link. The lists are linked from lowest to @@ -2651,7 +2654,7 @@ public: * If this has a child list, return it, even if the children are in * a different coordinate system to this item. */ - virtual nsDisplayList* GetChildren() const { return nullptr; } + virtual RetainedDisplayList* GetChildren() const { return nullptr; } /** * Returns the visible rect. @@ -3322,6 +3325,59 @@ private: nsDisplayList mLists[6]; }; +/** + * A display list that also retains the partial build + * information (in the form of a DAG) used to create it. + * + * Display lists built from a partial list aren't necessarily + * in the same order as a full build, and the DAG retains + * the information needing to interpret the current + * order correctly. + */ +class RetainedDisplayList : public nsDisplayList { +public: + RetainedDisplayList() {} + RetainedDisplayList(RetainedDisplayList&& aOther) + { + AppendToTop(&aOther); + mDAG = mozilla::Move(aOther.mDAG); + mKeyLookup.SwapElements(aOther.mKeyLookup); + } + ~RetainedDisplayList() + { + MOZ_ASSERT(mOldItems.IsEmpty(), "Must empty list before destroying"); + } + + RetainedDisplayList& operator=(RetainedDisplayList&& aOther) + { + MOZ_ASSERT(!Count(), "Can only move into an empty list!"); + MOZ_ASSERT(mOldItems.IsEmpty(), "Can only move into an empty list!"); + AppendToTop(&aOther); + mDAG = mozilla::Move(aOther.mDAG); + mKeyLookup.SwapElements(aOther.mKeyLookup); + return *this; + } + + void DeleteAll(nsDisplayListBuilder* aBuilder) + { + for (OldItemInfo& i : mOldItems) { + if (i.mItem) { + i.mItem->Destroy(aBuilder); + } + } + mOldItems.Clear(); + nsDisplayList::DeleteAll(aBuilder); + } + + void ClearDAG(); + + DirectedAcyclicGraph mDAG; + nsDataHashtable mKeyLookup; + + // Temporary state initialized during the preprocess pass + // of RetainedDisplayListBuilder and then used during merging. + nsTArray mOldItems; +}; class nsDisplayImageContainer : public nsDisplayItem { public: @@ -5001,7 +5057,7 @@ public: return mListPtr; } - virtual nsDisplayList* GetChildren() const override { return mListPtr; } + virtual RetainedDisplayList* GetChildren() const override { return mListPtr; } virtual int32_t ZIndex() const override { @@ -5047,8 +5103,8 @@ protected: mMergedFrames.AppendElements(aOther->mMergedFrames); } - nsDisplayList mList; - nsDisplayList* mListPtr; + RetainedDisplayList mList; + RetainedDisplayList* mListPtr; // The active scrolled root for the frame that created this // wrap list. RefPtr mFrameActiveScrolledRoot; @@ -6182,7 +6238,7 @@ public: return GetBounds(aBuilder, &snap); } - virtual nsDisplayList* GetChildren() const override + virtual RetainedDisplayList* GetChildren() const override { return mStoredList.GetChildren(); } @@ -6582,7 +6638,7 @@ public: return mList.GetChildren(); } - virtual nsDisplayList* GetChildren() const override + virtual RetainedDisplayList* GetChildren() const override { return mList.GetChildren(); } diff --git a/layout/reftests/position-dynamic-changes/relative/reftest.list b/layout/reftests/position-dynamic-changes/relative/reftest.list index 6c2142c4194c..5cc05e145d7b 100644 --- a/layout/reftests/position-dynamic-changes/relative/reftest.list +++ b/layout/reftests/position-dynamic-changes/relative/reftest.list @@ -1,5 +1,5 @@ -fuzzy-if(cocoaWidget,1,2) fuzzy-if(d2d,47,26) fuzzy-if(asyncPan&&!layersGPUAccelerated,149,716) == move-right-bottom.html move-right-bottom-ref.html -fuzzy-if(cocoaWidget,1,2) fuzzy-if(asyncPan&&!layersGPUAccelerated,149,716) == move-top-left.html move-top-left-ref.html # Bug 688545 +fuzzy-if(cocoaWidget,1,2) fuzzy-if(d2d,47,26) fuzzy-if(asyncPan&&!layersGPUAccelerated,169,970) == move-right-bottom.html move-right-bottom-ref.html +fuzzy-if(cocoaWidget,1,2) fuzzy-if(asyncPan&&!layersGPUAccelerated,169,970) == move-top-left.html move-top-left-ref.html # Bug 688545 fuzzy-if(cocoaWidget,1,3) fuzzy-if(asyncPan&&!layersGPUAccelerated,144,580) == move-right-bottom-table.html move-right-bottom-table-ref.html fuzzy-if(cocoaWidget,1,3) fuzzy-if(asyncPan&&!layersGPUAccelerated,144,580) == move-top-left-table.html move-top-left-table-ref.html # Bug 688545 == percent.html percent-ref.html diff --git a/layout/reftests/w3c-css/submitted/ruby/reftest.list b/layout/reftests/w3c-css/submitted/ruby/reftest.list index 25447e5e872b..b07479aacd55 100644 --- a/layout/reftests/w3c-css/submitted/ruby/reftest.list +++ b/layout/reftests/w3c-css/submitted/ruby/reftest.list @@ -1,6 +1,6 @@ # Tests for inlinizing block-level boxes == ruby-inlinize-blocks-001.html ruby-inlinize-blocks-001-ref.html -== ruby-inlinize-blocks-002.html ruby-inlinize-blocks-002-ref.html +fuzzy-if(winWidget,144,1) == ruby-inlinize-blocks-002.html ruby-inlinize-blocks-002-ref.html == ruby-inlinize-blocks-003.html ruby-inlinize-blocks-003-ref.html == ruby-inlinize-blocks-004.html ruby-inlinize-blocks-004-ref.html == ruby-inlinize-blocks-005.html ruby-inlinize-blocks-005-ref.html