diff --git a/gfx/layers/wr/DisplayItemCache.cpp b/gfx/layers/wr/DisplayItemCache.cpp index 84febb8b5910..6eb71f537d81 100644 --- a/gfx/layers/wr/DisplayItemCache.cpp +++ b/gfx/layers/wr/DisplayItemCache.cpp @@ -9,157 +9,134 @@ namespace mozilla { namespace layers { -static const size_t kInitialCacheSize = 1024; -static const size_t kMaximumCacheSize = 10240; -static const size_t kCacheThreshold = 1; - -DisplayItemCache::DisplayItemCache() - : mMaximumSize(0), mConsecutivePartialDisplayLists(0) { - if (XRE_IsContentProcess() && - StaticPrefs::gfx_webrender_enable_item_cache_AtStartup()) { - SetCapacity(kInitialCacheSize, kMaximumCacheSize); - } -} - -void DisplayItemCache::ClearCache() { - memset(mSlots.Elements(), 0, mSlots.Length() * sizeof(Slot)); - mFreeSlots.ClearAndRetainStorage(); - - for (size_t i = 0; i < CurrentSize(); ++i) { - mFreeSlots.AppendElement(i); - } -} - -Maybe DisplayItemCache::GetNextFreeSlot() { - if (mFreeSlots.IsEmpty() && !GrowIfPossible()) { - return Nothing(); - } - - return Some(mFreeSlots.PopLastElement()); -} - -bool DisplayItemCache::GrowIfPossible() { - if (IsFull()) { - return false; - } - - const auto currentSize = CurrentSize(); - MOZ_ASSERT(currentSize <= mMaximumSize); - - // New slots are added one by one, which is amortized O(1) time complexity due - // to underlying storage implementation. - mSlots.AppendElement(); - mFreeSlots.AppendElement(currentSize); - return true; -} - -void DisplayItemCache::FreeUnusedSlots() { - for (size_t i = 0; i < CurrentSize(); ++i) { - auto& slot = mSlots[i]; - - if (!slot.mUsed && slot.mOccupied) { - // This entry contained a cached item, but was not used. - slot.mOccupied = false; - mFreeSlots.AppendElement(i); - } - - slot.mUsed = false; - } -} - -void DisplayItemCache::SetCapacity(const size_t aInitialSize, - const size_t aMaximumSize) { - mMaximumSize = aMaximumSize; - mSlots.SetCapacity(aMaximumSize); - mSlots.SetLength(aInitialSize); - mFreeSlots.SetCapacity(aMaximumSize); - ClearCache(); -} - -Maybe DisplayItemCache::AssignSlot(nsPaintedDisplayItem* aItem) { - if (kCacheThreshold > mConsecutivePartialDisplayLists) { - // Wait for |kCacheThreshold| partial display list builds, before caching - // display items. This is meant to avoid caching overhead for interactions - // or pages that do not work well with retained display lists. - // TODO: This is a speculative optimization to minimize regressions. - return Nothing(); - } - - if (!aItem->CanBeReused()) { - // Do not try to cache items that cannot be reused. - return Nothing(); - } - - auto& slot = aItem->CacheIndex(); - if (!slot) { - slot = GetNextFreeSlot(); - if (!slot) { - // The item does not fit in the cache. - return Nothing(); - } - } - - MOZ_ASSERT(slot && CurrentSize() > *slot); - return slot; -} - -void DisplayItemCache::MarkSlotOccupied( - uint16_t aSlotIndex, const wr::WrSpaceAndClipChain& aSpaceAndClip) { - // Caching of the item succeeded, update the slot state. - auto& slot = mSlots[aSlotIndex]; - MOZ_ASSERT(!slot.mOccupied); - slot.mOccupied = true; - MOZ_ASSERT(!slot.mUsed); - slot.mUsed = true; - slot.mSpaceAndClip = aSpaceAndClip; -} - -Maybe DisplayItemCache::CanReuseItem( - nsPaintedDisplayItem* aItem, const wr::WrSpaceAndClipChain& aSpaceAndClip) { - auto& slotIndex = aItem->CacheIndex(); - if (!slotIndex) { - return Nothing(); - } - - MOZ_ASSERT(slotIndex && CurrentSize() > *slotIndex); - - auto& slot = mSlots[*slotIndex]; - if (!slot.mOccupied) { - // The display item has a stale cache slot. Recache the item. - return Nothing(); - } - - // Spatial id and clip id can change between display lists. - if (!(aSpaceAndClip == slot.mSpaceAndClip)) { - // Mark the cache slot inactive and recache the item. - slot.mOccupied = false; - return Nothing(); - } - - slot.mUsed = true; - return slotIndex; -} - void DisplayItemCache::UpdateState(const bool aPartialDisplayListBuildFailed, const wr::PipelineId& aPipelineId) { - const bool pipelineIdChanged = UpdatePipelineId(aPipelineId); - const bool invalidate = pipelineIdChanged || aPartialDisplayListBuildFailed; - - mConsecutivePartialDisplayLists = - invalidate ? 0 : mConsecutivePartialDisplayLists + 1; - - if (IsEmpty()) { - // The cache is empty so nothing needs to be updated. + if (!IsEnabled()) { return; } // Clear the cache if the partial display list build failed, or if the // pipeline id changed. - if (invalidate) { - ClearCache(); - } else { - FreeUnusedSlots(); + const bool clearCache = + UpdatePipelineId(aPipelineId) || aPartialDisplayListBuildFailed; + + if (clearCache) { + memset(mCachedItemState.Elements(), 0, + mCachedItemState.Length() * sizeof(CacheEntry)); + mNextIndex = 0; + mFreeList.Clear(); } + + PopulateFreeList(clearCache); +} + +void DisplayItemCache::PopulateFreeList(const bool aAddAll) { + uint16_t index = 0; + for (auto& state : mCachedItemState) { + if (aAddAll || (!state.mUsed && state.mCached)) { + // This entry contained a cached item, but was not used. + state.mCached = false; + mFreeList.AppendElement(index); + } + + state.mUsed = false; + index++; + } +} + +static bool CanCacheItem(const nsPaintedDisplayItem* aItem) { + // Only cache leaf display items that can be reused. + if (!aItem->CanBeReused()) { + return false; + } + + return aItem->CanBeCached(); +} + +void DisplayItemCache::MaybeStartCaching(nsPaintedDisplayItem* aItem, + wr::DisplayListBuilder& aBuilder) { + if (!IsEnabled()) { + return; + } + + Stats().AddTotal(); + + auto& index = aItem->CacheIndex(); + if (!index) { + if (!CanCacheItem(aItem)) { + // The item cannot be cached. + return; + } + + index = GetNextCacheIndex(); + if (!index) { + // The item does not fit in the cache. + return; + } + } + + // Update the current cache index, which is used by |MaybeEndCaching()| below. + MOZ_ASSERT(!mCurrentIndex); + mCurrentIndex = index; + + MOZ_ASSERT(CanCacheItem(aItem)); + MOZ_ASSERT(mCurrentIndex && CurrentCacheSize() > *mCurrentIndex); + + aBuilder.StartCachedItem(*mCurrentIndex); +} + +void DisplayItemCache::MaybeEndCaching(wr::DisplayListBuilder& aBuilder) { + if (!IsEnabled() || !mCurrentIndex) { + return; + } + + if (aBuilder.EndCachedItem(*mCurrentIndex)) { + // Caching of the item succeeded, update the cached item state. + auto& state = mCachedItemState[*mCurrentIndex]; + MOZ_ASSERT(!state.mCached); + state.mCached = true; + MOZ_ASSERT(!state.mUsed); + state.mUsed = true; + state.mSpaceAndClip = aBuilder.CurrentSpaceAndClipChain(); + + Stats().AddCached(); + } + + mCurrentIndex = Nothing(); +} + +bool DisplayItemCache::ReuseItem(nsPaintedDisplayItem* aItem, + wr::DisplayListBuilder& aBuilder) { + if (!IsEnabled()) { + return false; + } + + auto& index = aItem->CacheIndex(); + if (!index) { + return false; + } + + auto& state = mCachedItemState[*index]; + if (!state.mCached) { + // The display item has a stale cache state. + return false; // Recache item. + } + + // Spatial id and clip id can change between display lists. + if (!(aBuilder.CurrentSpaceAndClipChain() == state.mSpaceAndClip)) { + // TODO(miko): Technically we might be able to update just the changed data + // here but it adds a lot of complexity. + // Mark the cache state false and recache the item. + state.mCached = false; + return false; + } + + Stats().AddReused(); + Stats().AddTotal(); + + state.mUsed = true; + aBuilder.ReuseItem(*index); + return true; } } // namespace layers diff --git a/gfx/layers/wr/DisplayItemCache.h b/gfx/layers/wr/DisplayItemCache.h index cdebff9247e9..1b3391a5dee6 100644 --- a/gfx/layers/wr/DisplayItemCache.h +++ b/gfx/layers/wr/DisplayItemCache.h @@ -28,17 +28,7 @@ class CacheStats { void Reset() { mCached = mReused = mTotal = 0; } void Print() { - static uint64_t avgC = 1; - static uint64_t avgR = 1; - static uint64_t avgT = 1; - - avgC += mCached; - avgR += mReused; - avgT += mTotal; - - printf("Cached: %zu (avg: %f), Reused: %zu (avg: %f), Total: %zu\n", - mCached, (double)avgC / (double)avgT, mReused, - (double)avgR / (double)avgT, mTotal); + printf("Cached: %zu, Reused: %zu, Total: %zu\n", mCached, mReused, mTotal); } void AddCached() { mCached++; } @@ -62,24 +52,9 @@ class CacheStats { */ class DisplayItemCache final { public: - DisplayItemCache(); + DisplayItemCache() : mMaxCacheSize(0), mNextIndex(0) {} - /** - * Returns true if display item caching is enabled, otherwise false. - */ - bool IsEnabled() const { return mMaximumSize > 0; } - - /** - * Returns true if there are no cached items, otherwise false. - */ - bool IsEmpty() const { return mFreeSlots.Length() == CurrentSize(); } - - /** - * Returns true if the cache has reached the maximum size, otherwise false. - */ - bool IsFull() const { - return mFreeSlots.IsEmpty() && CurrentSize() == mMaximumSize; - } + bool IsEnabled() const { return mMaxCacheSize > 0; } /** * Updates the cache state based on the given display list build information @@ -94,50 +69,69 @@ class DisplayItemCache final { /** * Returns the current cache size. */ - size_t CurrentSize() const { return mSlots.Length(); } + size_t CurrentCacheSize() const { + return IsEnabled() ? mCachedItemState.Length() : 0; + } /** - * If there are free slots in the cache, assigns a cache slot to the given - * display item |aItem| and returns it. Otherwise returns Nothing(). + * Sets the initial and max cache size to given |aInitialSize| and |aMaxSize|. + * + * Currently the cache size is constant, but a good improvement would be to + * set the initial and maximum size based on the display list length. */ - Maybe AssignSlot(nsPaintedDisplayItem* aItem); + void SetCapacity(const size_t aInitialSize, const size_t aMaxSize) { + mMaxCacheSize = aMaxSize; + mCachedItemState.SetCapacity(aMaxSize); + mCachedItemState.SetLength(aInitialSize); + mFreeList.SetCapacity(aMaxSize); + } /** - * Marks the slot with the given |slotIndex| occupied and used. - * Also stores the current space and clipchain |aSpaceAndClip|. + * If the given display item |aItem| can be cached, update the cache state of + * the item and tell WR DisplayListBuilder |aBuilder| to cache WR display + * items until |EndCaching()| is called. + * + * If the display item cannot be cached, this function does nothing. */ - void MarkSlotOccupied(uint16_t slotIndex, - const wr::WrSpaceAndClipChain& aSpaceAndClip); + void MaybeStartCaching(nsPaintedDisplayItem* aItem, + wr::DisplayListBuilder& aBuilder); /** - * Returns the slot index of the the given display item |aItem|, if the item - * can be reused. The current space and clipchain |aSpaceAndClip| is used to - * check whether the cached item is still valid. - * If the item cannot be reused, returns Nothing(). + * Tell WR DisplayListBuilder |aBuilder| to stop caching WR display items. + * + * If the display item cannot be cached, this function does nothing. */ - Maybe CanReuseItem(nsPaintedDisplayItem* aItem, - const wr::WrSpaceAndClipChain& aSpaceAndClip); + void MaybeEndCaching(wr::DisplayListBuilder& aBuilder); + + /** + * If the given |aItem| has been cached, tell WR DisplayListBuilder |aBuilder| + * to reuse it. + * Returns true if the item was reused, otherwise returns false. + */ + bool ReuseItem(nsPaintedDisplayItem* aItem, wr::DisplayListBuilder& aBuilder); CacheStats& Stats() { return mCacheStats; } private: - struct Slot { - Slot() : mSpaceAndClip{}, mOccupied(false), mUsed(false) {} - + struct CacheEntry { wr::WrSpaceAndClipChain mSpaceAndClip; - bool mOccupied; + bool mCached; bool mUsed; }; - void ClearCache(); - void FreeUnusedSlots(); - bool GrowIfPossible(); - Maybe GetNextFreeSlot(); + Maybe GetNextCacheIndex() { + if (mFreeList.IsEmpty()) { + return Nothing(); + } + + return Some(mFreeList.PopLastElement()); + } /** - * Sets the initial and max cache size to given |aInitialSize| and |aMaxSize|. + * Iterates through |mCachedItemState| and adds unused entries to free list. + * If |aAddAll| is true, adds every entry regardless of the state. */ - void SetCapacity(const size_t aInitialSize, const size_t aMaximumSize); + void PopulateFreeList(const bool aAddAll); /** * Returns true if the given |aPipelineId| is different from the previous one, @@ -149,11 +143,12 @@ class DisplayItemCache final { return !isSame; } - size_t mMaximumSize; - nsTArray mSlots; - nsTArray mFreeSlots; + nsTArray mCachedItemState; + nsTArray mFreeList; + size_t mMaxCacheSize; + uint16_t mNextIndex; + Maybe mCurrentIndex; Maybe mPreviousPipelineId; - size_t mConsecutivePartialDisplayLists; CacheStats mCacheStats; }; diff --git a/gfx/layers/wr/WebRenderCommandBuilder.cpp b/gfx/layers/wr/WebRenderCommandBuilder.cpp index 0bb0e02943b1..7f0cbd0f1fca 100644 --- a/gfx/layers/wr/WebRenderCommandBuilder.cpp +++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp @@ -1552,7 +1552,12 @@ WebRenderCommandBuilder::WebRenderCommandBuilder( mLastAsr(nullptr), mDumpIndent(0), mDoGrouping(false), - mContainsSVGGroup(false) {} + mContainsSVGGroup(false) { + if (XRE_IsContentProcess() && + StaticPrefs::gfx_webrender_enable_item_cache_AtStartup()) { + mDisplayItemCache.SetCapacity(10000, 10000); + } +} void WebRenderCommandBuilder::Destroy() { mLastCanvasDatas.Clear(); @@ -1598,6 +1603,13 @@ void WebRenderCommandBuilder::BuildWebRenderCommands( mContainsSVGGroup = false; MOZ_ASSERT(mDumpIndent == 0); + if (mDisplayItemCache.IsEnabled()) { + mDisplayItemCache.UpdateState(aDisplayListBuilder->PartialBuildFailed(), + aBuilder.CurrentPipelineId()); + aBuilder.SetDisplayListCacheSize(mDisplayItemCache.CurrentCacheSize()); + // mDisplayItemCache.Stats().Reset(); + } + { nsPresContext* presContext = aDisplayListBuilder->RootReferenceFrame()->PresContext(); @@ -1667,6 +1679,10 @@ void WebRenderCommandBuilder::BuildWebRenderCommands( // Remove the user data those are not displayed on the screen and // also reset the data to unused for next transaction. RemoveUnusedAndResetWebRenderUserData(); + + if (mDisplayItemCache.IsEnabled()) { + // mDisplayItemCache.Stats().Print(); + } } bool WebRenderCommandBuilder::ShouldDumpDisplayList( @@ -1686,11 +1702,13 @@ void WebRenderCommandBuilder::CreateWebRenderCommands( auto* item = aItem->AsPaintedDisplayItem(); MOZ_RELEASE_ASSERT(item, "Tried to paint item that cannot be painted"); - if (aBuilder.ReuseItem(item)) { + if (mDisplayItemCache.ReuseItem(item, aBuilder)) { // No further processing should be needed, since the item was reused. return; } + mDisplayItemCache.MaybeStartCaching(item, aBuilder); + aItem->SetPaintRect(aItem->GetBuildingRect()); RenderRootStateManager* manager = mManager->GetRenderRootStateManager(aBuilder.GetRenderRoot()); @@ -1703,6 +1721,8 @@ void WebRenderCommandBuilder::CreateWebRenderCommands( if (!createdWRCommands) { PushItemAsImage(aItem, aBuilder, aResources, aSc, aDisplayListBuilder); } + + mDisplayItemCache.MaybeEndCaching(aBuilder); } void WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList( diff --git a/gfx/layers/wr/WebRenderCommandBuilder.h b/gfx/layers/wr/WebRenderCommandBuilder.h index 40a2cb728872..4b58bbfbb0f1 100644 --- a/gfx/layers/wr/WebRenderCommandBuilder.h +++ b/gfx/layers/wr/WebRenderCommandBuilder.h @@ -255,6 +255,8 @@ class WebRenderCommandBuilder final { wr::RenderRootArray mBuilderDumpIndex; wr::usize mDumpIndent; + DisplayItemCache mDisplayItemCache; + public: // Whether consecutive inactive display items should be grouped into one // blob image. diff --git a/gfx/layers/wr/WebRenderLayerManager.cpp b/gfx/layers/wr/WebRenderLayerManager.cpp index ea467d53b000..aa06e2725827 100644 --- a/gfx/layers/wr/WebRenderLayerManager.cpp +++ b/gfx/layers/wr/WebRenderLayerManager.cpp @@ -317,13 +317,11 @@ void WebRenderLayerManager::EndTransactionWithoutLayer( wr::DisplayListBuilder builder( WrBridge()->GetPipeline(), wrRects[wr::RenderRoot::Default].size, - mLastDisplayListSizes[wr::RenderRoot::Default], &mDisplayItemCache); - + mLastDisplayListSizes[wr::RenderRoot::Default]); for (auto renderRoot : wr::kNonDefaultRenderRoots) { if (!rects[renderRoot].IsEmpty()) { builder.CreateSubBuilder(wrRects[renderRoot].size, - mLastDisplayListSizes[renderRoot], nullptr, - renderRoot); + mLastDisplayListSizes[renderRoot], renderRoot); } } @@ -347,14 +345,9 @@ void WebRenderLayerManager::EndTransactionWithoutLayer( // generating the WR display list is the closest equivalent PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Layerization); - builder.UpdateCacheState(aDisplayListBuilder->PartialBuildFailed()); - mWebRenderCommandBuilder.BuildWebRenderCommands( builder, resourceUpdates, aDisplayList, aDisplayListBuilder, mScrollDatas, std::move(aFilters)); - - builder.UpdateCacheSize(); - builderDumpIndex = mWebRenderCommandBuilder.GetBuilderDumpIndex(builder.GetRenderRoot()); containsSVGGroup = mWebRenderCommandBuilder.GetContainsSVGGroup(); diff --git a/gfx/layers/wr/WebRenderLayerManager.h b/gfx/layers/wr/WebRenderLayerManager.h index 1c79e6c65e6c..73c99dc76da2 100644 --- a/gfx/layers/wr/WebRenderLayerManager.h +++ b/gfx/layers/wr/WebRenderLayerManager.h @@ -238,7 +238,6 @@ class WebRenderLayerManager final : public LayerManager { wr::RenderRootArray mLastDisplayListSizes; wr::RenderRootArray mStateManagers; - DisplayItemCache mDisplayItemCache; }; } // namespace layers diff --git a/gfx/webrender_bindings/WebRenderAPI.cpp b/gfx/webrender_bindings/WebRenderAPI.cpp index 9eed8e01fc3e..e5990c4730f1 100644 --- a/gfx/webrender_bindings/WebRenderAPI.cpp +++ b/gfx/webrender_bindings/WebRenderAPI.cpp @@ -924,16 +924,13 @@ void WebRenderAPI::RunOnRenderThread(UniquePtr aEvent) { DisplayListBuilder::DisplayListBuilder(PipelineId aId, const wr::LayoutSize& aContentSize, - size_t aCapacity, - layers::DisplayItemCache* aCache, - RenderRoot aRenderRoot) + size_t aCapacity, RenderRoot aRenderRoot) : mCurrentSpaceAndClipChain(wr::RootScrollNodeWithChain()), mActiveFixedPosTracker(nullptr), mPipelineId(aId), mContentSize(aContentSize), mRenderRoot(aRenderRoot), - mSendSubBuilderDisplayList(aRenderRoot == wr::RenderRoot::Default), - mDisplayItemCache(aCache) { + mSendSubBuilderDisplayList(aRenderRoot == wr::RenderRoot::Default) { MOZ_COUNT_CTOR(DisplayListBuilder); mWrState = wr_state_new(aId, aContentSize, aCapacity); } @@ -949,11 +946,11 @@ void DisplayListBuilder::ClearSave() { wr_dp_clear_save(mWrState); } DisplayListBuilder& DisplayListBuilder::CreateSubBuilder( const wr::LayoutSize& aContentSize, size_t aCapacity, - layers::DisplayItemCache* aCache, wr::RenderRoot aRenderRoot) { + wr::RenderRoot aRenderRoot) { MOZ_ASSERT(mRenderRoot == wr::RenderRoot::Default); MOZ_ASSERT(!mSubBuilders[aRenderRoot]); mSubBuilders[aRenderRoot] = MakeUnique( - mPipelineId, aContentSize, aCapacity, aCache, aRenderRoot); + mPipelineId, aContentSize, aCapacity, aRenderRoot); return *mSubBuilders[aRenderRoot]; } @@ -1488,83 +1485,20 @@ void DisplayListBuilder::PushBoxShadow( aBorderRadius, aClipMode); } -void DisplayListBuilder::StartGroup(nsPaintedDisplayItem* aItem) { - if (!mDisplayItemCache || mDisplayItemCache->IsFull()) { - return; - } - - MOZ_ASSERT(!mCurrentCacheSlot); - mCurrentCacheSlot = mDisplayItemCache->AssignSlot(aItem); - - if (mCurrentCacheSlot) { - wr_dp_start_item_group(mWrState, mCurrentCacheSlot.ref()); - } +void DisplayListBuilder::ReuseItem(wr::ItemKey aKey) { + wr_dp_push_reuse_item(mWrState, aKey); } -void DisplayListBuilder::CancelGroup() { - if (!mDisplayItemCache || !mCurrentCacheSlot) { - return; - } - - wr_dp_cancel_item_group(mWrState); - mCurrentCacheSlot = Nothing(); +void DisplayListBuilder::StartCachedItem(wr::ItemKey aKey) { + wr_dp_start_cached_item(mWrState, aKey); } -void DisplayListBuilder::FinishGroup() { - if (!mDisplayItemCache || !mCurrentCacheSlot) { - return; - } - - MOZ_ASSERT(mCurrentCacheSlot); - - if (wr_dp_finish_item_group(mWrState, mCurrentCacheSlot.ref())) { - mDisplayItemCache->MarkSlotOccupied(mCurrentCacheSlot.ref(), - CurrentSpaceAndClipChain()); - mDisplayItemCache->Stats().AddCached(); - } - - mCurrentCacheSlot = Nothing(); +bool DisplayListBuilder::EndCachedItem(wr::ItemKey aKey) { + return wr_dp_end_cached_item(mWrState, aKey); } -bool DisplayListBuilder::ReuseItem(nsPaintedDisplayItem* aItem) { - if (!mDisplayItemCache) { - return false; - } - - mDisplayItemCache->Stats().AddTotal(); - - if (mDisplayItemCache->IsEmpty()) { - return false; - } - - Maybe slot = - mDisplayItemCache->CanReuseItem(aItem, CurrentSpaceAndClipChain()); - - if (slot) { - mDisplayItemCache->Stats().AddReused(); - wr_dp_push_reuse_items(mWrState, slot.ref()); - return true; - } - - return false; -} - -void DisplayListBuilder::UpdateCacheState( - const bool aPartialDisplayListBuildFailed) { - if (mDisplayItemCache && mDisplayItemCache->IsEnabled()) { - mDisplayItemCache->UpdateState(aPartialDisplayListBuildFailed, - CurrentPipelineId()); -#if 0 - mDisplayItemCache->Stats().Print(); - mDisplayItemCache->Stats().Reset(); -#endif - } -} - -void DisplayListBuilder::UpdateCacheSize() { - if (mDisplayItemCache && mDisplayItemCache->IsEnabled()) { - wr_dp_set_cache_size(mWrState, mDisplayItemCache->CurrentSize()); - } +void DisplayListBuilder::SetDisplayListCacheSize(const size_t aCacheSize) { + wr_dp_set_cache_size(mWrState, aCacheSize); } Maybe diff --git a/gfx/webrender_bindings/WebRenderAPI.h b/gfx/webrender_bindings/WebRenderAPI.h index 7dade32ca10d..f91556ee1ebd 100644 --- a/gfx/webrender_bindings/WebRenderAPI.h +++ b/gfx/webrender_bindings/WebRenderAPI.h @@ -25,7 +25,6 @@ #include "Units.h" class nsDisplayItem; -class nsPaintedDisplayItem; class nsDisplayTransform; #undef None @@ -40,7 +39,6 @@ class CompositorWidget; namespace layers { class CompositorBridgeParent; -class DisplayItemCache; class WebRenderBridgeParent; class RenderRootStateManager; struct RenderRootDisplayListData; @@ -413,7 +411,6 @@ class DisplayListBuilder final { public: DisplayListBuilder(wr::PipelineId aId, const wr::LayoutSize& aContentSize, size_t aCapacity = 0, - layers::DisplayItemCache* aCache = nullptr, RenderRoot aRenderRoot = RenderRoot::Default); DisplayListBuilder(DisplayListBuilder&&) = default; @@ -435,7 +432,6 @@ class DisplayListBuilder final { bool HasSubBuilder(RenderRoot aRenderRoot); DisplayListBuilder& CreateSubBuilder(const wr::LayoutSize& aContentSize, size_t aCapacity, - layers::DisplayItemCache* aCache, RenderRoot aRenderRoot); DisplayListBuilder& SubBuilder(RenderRoot aRenderRoot); @@ -626,33 +622,10 @@ class DisplayListBuilder final { const wr::BorderRadius& aBorderRadius, const wr::BoxShadowClipMode& aClipMode); - /** - * Notifies the DisplayListBuilder that it can group together WR display items - * that are pushed until |CancelGroup()| or |FinishGroup()| call. - */ - void StartGroup(nsPaintedDisplayItem* aItem); - - /** - * Cancels grouping of the display items and discards all the display items - * pushed between the |StartGroup()| and |CancelGroup()| calls. - */ - void CancelGroup(); - - /** - * Finishes the display item group. The group is stored in WebRender backend, - * and can be reused with |ReuseItem()|, if the Gecko display item is reused. - */ - void FinishGroup(); - - /** - * Try to reuse the previously created WebRender display items for the given - * Gecko display item |aItem|. - * Returns true if the items were reused, otherwise returns false. - */ - bool ReuseItem(nsPaintedDisplayItem* aItem); - - void UpdateCacheState(const bool aPartialDisplayListBuildFailed); - void UpdateCacheSize(); + void StartCachedItem(wr::ItemKey aKey); + bool EndCachedItem(wr::ItemKey aKey); + void ReuseItem(wr::ItemKey aKey); + void SetDisplayListCacheSize(const size_t aCacheSize); uint64_t CurrentClipChainId() const { return mCurrentSpaceAndClipChain.clip_chain; @@ -759,9 +732,6 @@ class DisplayListBuilder final { RenderRoot mRenderRoot; bool mSendSubBuilderDisplayList; - layers::DisplayItemCache* mDisplayItemCache; - Maybe mCurrentCacheSlot; - friend class WebRenderAPI; friend class SpaceAndClipChainHelper; }; diff --git a/gfx/webrender_bindings/src/bindings.rs b/gfx/webrender_bindings/src/bindings.rs index 80783b0f535d..26ae42cfbdaf 100644 --- a/gfx/webrender_bindings/src/bindings.rs +++ b/gfx/webrender_bindings/src/bindings.rs @@ -33,11 +33,11 @@ use program_cache::{remove_disk_cache, WrProgramCache}; use rayon; use thread_profiler::register_thread_with_profiler; use webrender::{ - api::units::*, api::*, set_profiler_hooks, ApiRecordingReceiver, AsyncPropertySampler, AsyncScreenshotHandle, - BinaryRecorder, Compositor, CompositorCapabilities, CompositorConfig, DebugFlags, Device, NativeSurfaceId, - NativeSurfaceInfo, NativeTileId, PipelineInfo, ProfilerHooks, RecordedFrameHandle, Renderer, RendererOptions, - RendererStats, SceneBuilderHooks, ShaderPrecacheFlags, Shaders, ThreadListener, UploadMethod, VertexUsageHint, - WrShaders, + api::*, api::units::*, ApiRecordingReceiver, AsyncPropertySampler, AsyncScreenshotHandle, + BinaryRecorder, Compositor, CompositorCapabilities, DebugFlags, Device, + NativeSurfaceId, PipelineInfo, ProfilerHooks, RecordedFrameHandle, Renderer, RendererOptions, RendererStats, + SceneBuilderHooks, ShaderPrecacheFlags, Shaders, ThreadListener, UploadMethod, VertexUsageHint, + WrShaders, set_profiler_hooks, CompositorConfig, NativeSurfaceInfo, NativeTileId }; #[cfg(target_os = "macos")] @@ -972,7 +972,9 @@ pub struct WrHitTester { } #[no_mangle] -pub extern "C" fn wr_api_request_hit_tester(dh: &DocumentHandle) -> *mut WrHitTester { +pub extern "C" fn wr_api_request_hit_tester( + dh: &DocumentHandle, +) -> *mut WrHitTester { let hit_tester = dh.api.request_hit_tester(dh.document_id); Box::into_raw(Box::new(WrHitTester { ptr: hit_tester })) } @@ -983,15 +985,20 @@ pub unsafe extern "C" fn wr_hit_tester_clone(hit_tester: *mut WrHitTester) -> *m Box::into_raw(Box::new(WrHitTester { ptr: new_ref })) } + #[no_mangle] pub extern "C" fn wr_hit_tester_hit_test( hit_tester: &WrHitTester, point: WorldPoint, out_pipeline_id: &mut WrPipelineId, out_scroll_id: &mut u64, - out_hit_info: &mut u16, + out_hit_info: &mut u16 ) -> bool { - let result = hit_tester.ptr.hit_test(None, point, HitTestFlags::empty()); + let result = hit_tester.ptr.hit_test( + None, + point, + HitTestFlags::empty() + ); for item in &result.items { // For now we should never be getting results back for which the tag is @@ -1205,8 +1212,13 @@ extern "C" { clip_rect: DeviceIntRect, ); fn wr_compositor_end_frame(compositor: *mut c_void); - fn wr_compositor_enable_native_compositor(compositor: *mut c_void, enable: bool); - fn wr_compositor_get_capabilities(compositor: *mut c_void) -> CompositorCapabilities; + fn wr_compositor_enable_native_compositor( + compositor: *mut c_void, + enable: bool, + ); + fn wr_compositor_get_capabilities( + compositor: *mut c_void, + ) -> CompositorCapabilities; } pub struct WrCompositor(*mut c_void); @@ -1220,7 +1232,13 @@ impl Compositor for WrCompositor { is_opaque: bool, ) { unsafe { - wr_compositor_create_surface(self.0, id, virtual_offset, tile_size, is_opaque); + wr_compositor_create_surface( + self.0, + id, + virtual_offset, + tile_size, + is_opaque, + ); } } @@ -1293,7 +1311,9 @@ impl Compositor for WrCompositor { } fn get_capabilities(&self) -> CompositorCapabilities { - unsafe { wr_compositor_get_capabilities(self.0) } + unsafe { + wr_compositor_get_capabilities(self.0) + } } } @@ -2546,7 +2566,10 @@ pub extern "C" fn wr_dp_push_iframe( } // A helper fn to construct a PrimitiveFlags -fn prim_flags(is_backface_visible: bool, prefer_compositor_surface: bool) -> PrimitiveFlags { +fn prim_flags( + is_backface_visible: bool, + prefer_compositor_surface: bool, +) -> PrimitiveFlags { let mut flags = PrimitiveFlags::empty(); if is_backface_visible { @@ -2796,18 +2819,16 @@ pub extern "C" fn wr_dp_push_clear_rect_with_parent_clip( } #[no_mangle] -pub extern "C" fn wr_dp_push_image( - state: &mut WrState, - bounds: LayoutRect, - clip: LayoutRect, - is_backface_visible: bool, - parent: &WrSpaceAndClipChain, - image_rendering: ImageRendering, - key: WrImageKey, - premultiplied_alpha: bool, - color: ColorF, - prefer_compositor_surface: bool, -) { +pub extern "C" fn wr_dp_push_image(state: &mut WrState, + bounds: LayoutRect, + clip: LayoutRect, + is_backface_visible: bool, + parent: &WrSpaceAndClipChain, + image_rendering: ImageRendering, + key: WrImageKey, + premultiplied_alpha: bool, + color: ColorF, + prefer_compositor_surface: bool) { debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() }); let space_and_clip = parent.to_webrender(state.pipeline_id); @@ -2880,21 +2901,19 @@ pub extern "C" fn wr_dp_push_repeating_image( /// Push a 3 planar yuv image. #[no_mangle] -pub extern "C" fn wr_dp_push_yuv_planar_image( - state: &mut WrState, - bounds: LayoutRect, - clip: LayoutRect, - is_backface_visible: bool, - parent: &WrSpaceAndClipChain, - image_key_0: WrImageKey, - image_key_1: WrImageKey, - image_key_2: WrImageKey, - color_depth: WrColorDepth, - color_space: WrYuvColorSpace, - color_range: WrColorRange, - image_rendering: ImageRendering, - prefer_compositor_surface: bool, -) { +pub extern "C" fn wr_dp_push_yuv_planar_image(state: &mut WrState, + bounds: LayoutRect, + clip: LayoutRect, + is_backface_visible: bool, + parent: &WrSpaceAndClipChain, + image_key_0: WrImageKey, + image_key_1: WrImageKey, + image_key_2: WrImageKey, + color_depth: WrColorDepth, + color_space: WrYuvColorSpace, + color_range: WrColorRange, + image_rendering: ImageRendering, + prefer_compositor_surface: bool) { debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() }); let space_and_clip = parent.to_webrender(state.pipeline_id); @@ -2921,20 +2940,18 @@ pub extern "C" fn wr_dp_push_yuv_planar_image( /// Push a 2 planar NV12 image. #[no_mangle] -pub extern "C" fn wr_dp_push_yuv_NV12_image( - state: &mut WrState, - bounds: LayoutRect, - clip: LayoutRect, - is_backface_visible: bool, - parent: &WrSpaceAndClipChain, - image_key_0: WrImageKey, - image_key_1: WrImageKey, - color_depth: WrColorDepth, - color_space: WrYuvColorSpace, - color_range: WrColorRange, - image_rendering: ImageRendering, - prefer_compositor_surface: bool, -) { +pub extern "C" fn wr_dp_push_yuv_NV12_image(state: &mut WrState, + bounds: LayoutRect, + clip: LayoutRect, + is_backface_visible: bool, + parent: &WrSpaceAndClipChain, + image_key_0: WrImageKey, + image_key_1: WrImageKey, + color_depth: WrColorDepth, + color_space: WrYuvColorSpace, + color_range: WrColorRange, + image_rendering: ImageRendering, + prefer_compositor_surface: bool) { debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() }); let space_and_clip = parent.to_webrender(state.pipeline_id); @@ -2961,19 +2978,17 @@ pub extern "C" fn wr_dp_push_yuv_NV12_image( /// Push a yuv interleaved image. #[no_mangle] -pub extern "C" fn wr_dp_push_yuv_interleaved_image( - state: &mut WrState, - bounds: LayoutRect, - clip: LayoutRect, - is_backface_visible: bool, - parent: &WrSpaceAndClipChain, - image_key_0: WrImageKey, - color_depth: WrColorDepth, - color_space: WrYuvColorSpace, - color_range: WrColorRange, - image_rendering: ImageRendering, - prefer_compositor_surface: bool, -) { +pub extern "C" fn wr_dp_push_yuv_interleaved_image(state: &mut WrState, + bounds: LayoutRect, + clip: LayoutRect, + is_backface_visible: bool, + parent: &WrSpaceAndClipChain, + image_key_0: WrImageKey, + color_depth: WrColorDepth, + color_space: WrYuvColorSpace, + color_range: WrColorRange, + image_rendering: ImageRendering, + prefer_compositor_surface: bool) { debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() }); let space_and_clip = parent.to_webrender(state.pipeline_id); @@ -3543,26 +3558,25 @@ pub extern "C" fn wr_dp_start_cached_item(state: &mut WrState, key: ItemKey) { debug_assert!(state.current_item_key.is_none(), "Nested item keys"); state.current_item_key = Some(key); - state.frame_builder.dl_builder.start_item_group(key); + state.frame_builder.dl_builder.start_extra_data_chunk(); } #[no_mangle] -pub extern "C" fn wr_dp_cancel_item_group(state: &mut WrState) { - state.current_item_key = None; +pub extern "C" fn wr_dp_end_cached_item(state: &mut WrState, key: ItemKey) -> bool { + let _previous = state.current_item_key.take().expect("Nested item keys"); + debug_assert!(_previous == key, "Item key changed during caching"); - state.frame_builder.dl_builder.cancel_item_group(); + if !state.frame_builder.dl_builder.end_extra_data_chunk() { + return false; + } + + state.frame_builder.dl_builder.push_reuse_item(key); + true } #[no_mangle] -pub extern "C" fn wr_dp_finish_item_group(state: &mut WrState, key: ItemKey) -> bool { - state.current_item_key = None; - - state.frame_builder.dl_builder.finish_item_group(key) -} - -#[no_mangle] -pub extern "C" fn wr_dp_push_reuse_items(state: &mut WrState, key: ItemKey) { - state.frame_builder.dl_builder.push_reuse_items(key); +pub extern "C" fn wr_dp_push_reuse_item(state: &mut WrState, key: ItemKey) { + state.frame_builder.dl_builder.push_reuse_item(key); } #[no_mangle] diff --git a/gfx/wr/webrender_api/src/display_item_cache.rs b/gfx/wr/webrender_api/src/display_item_cache.rs index 9118bd7c4a92..53f5b79db5e4 100644 --- a/gfx/wr/webrender_api/src/display_item_cache.rs +++ b/gfx/wr/webrender_api/src/display_item_cache.rs @@ -68,8 +68,8 @@ impl DisplayItemCache { ) { if capacity > self.items.len() { self.items.resize_with(capacity, || None::); - // println!("Current cache size: {:?} elements, {:?} bytes", - // capacity, std::mem::size_of::() * capacity); + // println!("Current cache size: {:?}", + // mem::size_of::() * capacity); } } diff --git a/gfx/wr/webrender_api/src/display_list.rs b/gfx/wr/webrender_api/src/display_list.rs index 9cf4dd89336f..2f7d3f1aff34 100644 --- a/gfx/wr/webrender_api/src/display_list.rs +++ b/gfx/wr/webrender_api/src/display_list.rs @@ -911,7 +911,7 @@ pub struct DisplayListBuilder { pub pipeline_id: PipelineId, extra_data: Vec, - extra_data_chunk_start: usize, + extra_data_chunk_len: usize, writing_extra_data_chunk: bool, next_clip_index: usize, @@ -945,7 +945,7 @@ impl DisplayListBuilder { pipeline_id, extra_data: Vec::new(), - extra_data_chunk_start: 0, + extra_data_chunk_len: 0, writing_extra_data_chunk: false, next_clip_index: FIRST_CLIP_NODE_INDEX, @@ -1744,38 +1744,29 @@ impl DisplayListBuilder { self.push_item(&di::DisplayItem::PopAllShadows); } - fn truncate_extra_data_chunk(&mut self) { - self.extra_data.truncate(self.extra_data_chunk_start) - } - - pub fn start_item_group(&mut self, _key: di::ItemKey) { + pub fn start_extra_data_chunk(&mut self) { self.writing_extra_data_chunk = true; - self.extra_data_chunk_start = self.extra_data.len(); + self.extra_data_chunk_len = self.extra_data.len(); } - pub fn finish_item_group(&mut self, key: di::ItemKey) -> bool { + /// Returns true, if any bytes were written to extra data buffer. + pub fn end_extra_data_chunk(&mut self) -> bool { self.writing_extra_data_chunk = false; - - let chunk_size = self.extra_data.len() - self.extra_data_chunk_start; - if chunk_size > 0 { - self.push_reuse_items(key); - return true - } - - false + (self.extra_data.len() - self.extra_data_chunk_len) > 0 } - pub fn cancel_item_group(&mut self) { - debug_assert!(self.writing_extra_data_chunk); - self.writing_extra_data_chunk = false; - self.truncate_extra_data_chunk(); + pub fn push_reuse_item( + &mut self, + key: di::ItemKey, + ) { + let item = di::DisplayItem::ReuseItem(key); + self.push_item(&item); } - pub fn push_reuse_items(&mut self, key: di::ItemKey) { - self.push_item(&di::DisplayItem::ReuseItem(key)); - } - - pub fn set_cache_size(&mut self, cache_size: usize) { + pub fn set_cache_size( + &mut self, + cache_size: usize, + ) { self.cache_size = cache_size; } diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp index 478bc50b915c..1de51b2fa0bc 100644 --- a/layout/painting/nsDisplayList.cpp +++ b/layout/painting/nsDisplayList.cpp @@ -5185,10 +5185,8 @@ bool nsDisplayBackgroundColor::CreateWebRenderCommands( aBuilder.PushRectWithAnimation(r, r, !BackfaceIsHidden(), wr::ToColorF(ToDeviceColor(mColor)), &prop); } else { - aBuilder.StartGroup(this); aBuilder.PushRect(r, r, !BackfaceIsHidden(), wr::ToColorF(ToDeviceColor(mColor))); - aBuilder.FinishGroup(); } return true; @@ -5542,9 +5540,7 @@ bool nsDisplayCompositorHitTestInfo::CreateWebRenderCommands( const wr::LayoutRect rect = wr::ToLayoutRect(devRect); - aBuilder.StartGroup(this); aBuilder.PushHitTest(rect, rect, !BackfaceIsHidden()); - aBuilder.FinishGroup(); aBuilder.ClearHitTestInfo(); return true; diff --git a/layout/painting/nsDisplayList.h b/layout/painting/nsDisplayList.h index 81d69ede35e0..bce58d6403d1 100644 --- a/layout/painting/nsDisplayList.h +++ b/layout/painting/nsDisplayList.h @@ -3215,6 +3215,16 @@ class nsPaintedDisplayItem : public nsDisplayItem { MOZ_ASSERT_UNREACHABLE("Paint() is not implemented!"); } + /** + * Display items that are guaranteed to produce the same output from + * |CreateWebRenderCommands()|, regardless of the surrounding state, + * can return true. This allows |DisplayItemCache| to cache the output of + * |CreateWebRenderCommands()|, and avoid the call for successive paints, if + * the item is reused. If calling |CreateWebRenderCommands()| would not create + * any WebRender display items, |CanBeCached()| should return false. + */ + virtual bool CanBeCached() const { return false; } + /** * External storage used by |DisplayItemCache| to avoid hashmap lookups. * If an item is reused and has the cache index set, it means that @@ -4913,6 +4923,8 @@ class nsDisplayBackgroundColor : public nsPaintedDisplayItem { } } + bool CanBeCached() const final { return !HasBackgroundClipText(); } + NS_DISPLAY_DECL_NAME("BackgroundColor", TYPE_BACKGROUND_COLOR) void RestoreState() override { @@ -5303,6 +5315,12 @@ class nsDisplayCompositorHitTestInfo : public nsDisplayHitTestInfoBase { MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayCompositorHitTestInfo) + bool CanBeCached() const final { + // Do not try to cache gecko hit test items with empty hit test area, + // because they would not create any WebRender display items. + return !HitTestArea().IsEmpty(); + } + NS_DISPLAY_DECL_NAME("CompositorHitTestInfo", TYPE_COMPOSITOR_HITTEST_INFO) void InitializeScrollTarget(nsDisplayListBuilder* aBuilder);