/* -*- 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 "DisplayItemCache.h" #include "nsDisplayList.h" namespace mozilla { namespace layers { DisplayItemCache::DisplayItemCache() : mDisplayList(nullptr), mMaximumSize(0), mPipelineId{}, mCaching(false), mInvalid(false), mSuppressed(false) {} void DisplayItemCache::SetDisplayList(nsDisplayListBuilder* aBuilder, nsDisplayList* aList) { if (!IsEnabled()) { return; } MOZ_ASSERT(aBuilder); MOZ_ASSERT(aList); const bool listChanged = mDisplayList != aList; const bool partialBuild = !aBuilder->PartialBuildFailed(); if (listChanged && partialBuild) { // If the display list changed during a partial update, it means that // |SetDisplayList()| has missed one rebuilt display list. mDisplayList = nullptr; return; } if (listChanged || !partialBuild) { // The display list has been changed or rebuilt. mDisplayList = aList; mInvalid = true; } UpdateState(); } void DisplayItemCache::SetPipelineId(const wr::PipelineId& aPipelineId) { mInvalid = mInvalid || !(mPipelineId == aPipelineId); mPipelineId = aPipelineId; } void DisplayItemCache::UpdateState() { // |mCaching == true| if: // 1) |SetDisplayList()| is called with a fully rebuilt display list // followed by // 2a) |SetDisplayList()| is called with a partially updated display list // or // 2b) |SkipWaitingForPartialDisplayList()| is called mCaching = !mInvalid; #if 0 Stats().Print(); Stats().Reset(); #endif if (IsEmpty()) { // The cache is empty so nothing needs to be updated. mInvalid = false; return; } // Clear the cache if the current state is invalid. if (mInvalid) { Clear(); } else { FreeUnusedSlots(); } mInvalid = false; } void DisplayItemCache::Clear() { 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.SetLength(aInitialSize); mFreeSlots.SetCapacity(aMaximumSize); Clear(); } Maybe DisplayItemCache::AssignSlot(nsPaintedDisplayItem* aItem) { if (!mCaching || !aItem->CanBeReused() || !aItem->CanBeCached()) { 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(); } if (mSuppressed) { slot.mOccupied = false; slotIndex = Nothing(); return Nothing(); } if (!(aSpaceAndClip == slot.mSpaceAndClip)) { // Spatial id and clip id can change between display lists, if items that // generate them change their order. slot.mOccupied = false; aItem->SetCantBeCached(); slotIndex = Nothing(); } else { slot.mUsed = true; } return slotIndex; } } // namespace layers } // namespace mozilla