1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "DisplayItemCache.h"
8 #include "nsDisplayList.h"
9
10 namespace mozilla {
11 namespace layers {
12
DisplayItemCache()13 DisplayItemCache::DisplayItemCache()
14 : mDisplayList(nullptr),
15 mMaximumSize(0),
16 mPipelineId{},
17 mCaching(false),
18 mInvalid(false),
19 mSuppressed(false) {}
20
SetDisplayList(nsDisplayListBuilder * aBuilder,nsDisplayList * aList)21 void DisplayItemCache::SetDisplayList(nsDisplayListBuilder* aBuilder,
22 nsDisplayList* aList) {
23 if (!IsEnabled()) {
24 return;
25 }
26
27 MOZ_ASSERT(aBuilder);
28 MOZ_ASSERT(aList);
29
30 const bool listChanged = mDisplayList != aList;
31 const bool partialBuild = !aBuilder->PartialBuildFailed();
32
33 if (listChanged && partialBuild) {
34 // If the display list changed during a partial update, it means that
35 // |SetDisplayList()| has missed one rebuilt display list.
36 mDisplayList = nullptr;
37 return;
38 }
39
40 if (listChanged || !partialBuild) {
41 // The display list has been changed or rebuilt.
42 mDisplayList = aList;
43 mInvalid = true;
44 }
45
46 UpdateState();
47 }
48
SetPipelineId(const wr::PipelineId & aPipelineId)49 void DisplayItemCache::SetPipelineId(const wr::PipelineId& aPipelineId) {
50 mInvalid = mInvalid || !(mPipelineId == aPipelineId);
51 mPipelineId = aPipelineId;
52 }
53
UpdateState()54 void DisplayItemCache::UpdateState() {
55 // |mCaching == true| if:
56 // 1) |SetDisplayList()| is called with a fully rebuilt display list
57 // followed by
58 // 2a) |SetDisplayList()| is called with a partially updated display list
59 // or
60 // 2b) |SkipWaitingForPartialDisplayList()| is called
61 mCaching = !mInvalid;
62
63 #if 0
64 Stats().Print();
65 Stats().Reset();
66 #endif
67
68 if (IsEmpty()) {
69 // The cache is empty so nothing needs to be updated.
70 mInvalid = false;
71 return;
72 }
73
74 // Clear the cache if the current state is invalid.
75 if (mInvalid) {
76 Clear();
77 } else {
78 FreeUnusedSlots();
79 }
80
81 mInvalid = false;
82 }
83
Clear()84 void DisplayItemCache::Clear() {
85 memset(mSlots.Elements(), 0, mSlots.Length() * sizeof(Slot));
86 mFreeSlots.ClearAndRetainStorage();
87
88 for (size_t i = 0; i < CurrentSize(); ++i) {
89 mFreeSlots.AppendElement(i);
90 }
91 }
92
GetNextFreeSlot()93 Maybe<uint16_t> DisplayItemCache::GetNextFreeSlot() {
94 if (mFreeSlots.IsEmpty() && !GrowIfPossible()) {
95 return Nothing();
96 }
97
98 return Some(mFreeSlots.PopLastElement());
99 }
100
GrowIfPossible()101 bool DisplayItemCache::GrowIfPossible() {
102 if (IsFull()) {
103 return false;
104 }
105
106 const auto currentSize = CurrentSize();
107 MOZ_ASSERT(currentSize <= mMaximumSize);
108
109 // New slots are added one by one, which is amortized O(1) time complexity due
110 // to underlying storage implementation.
111 mSlots.AppendElement();
112 mFreeSlots.AppendElement(currentSize);
113 return true;
114 }
115
FreeUnusedSlots()116 void DisplayItemCache::FreeUnusedSlots() {
117 for (size_t i = 0; i < CurrentSize(); ++i) {
118 auto& slot = mSlots[i];
119
120 if (!slot.mUsed && slot.mOccupied) {
121 // This entry contained a cached item, but was not used.
122 slot.mOccupied = false;
123 mFreeSlots.AppendElement(i);
124 }
125
126 slot.mUsed = false;
127 }
128 }
129
SetCapacity(const size_t aInitialSize,const size_t aMaximumSize)130 void DisplayItemCache::SetCapacity(const size_t aInitialSize,
131 const size_t aMaximumSize) {
132 mMaximumSize = aMaximumSize;
133 mSlots.SetLength(aInitialSize);
134 mFreeSlots.SetCapacity(aMaximumSize);
135 Clear();
136 }
137
AssignSlot(nsPaintedDisplayItem * aItem)138 Maybe<uint16_t> DisplayItemCache::AssignSlot(nsPaintedDisplayItem* aItem) {
139 if (!mCaching || !aItem->CanBeReused() || !aItem->CanBeCached()) {
140 return Nothing();
141 }
142
143 auto& slot = aItem->CacheIndex();
144
145 if (!slot) {
146 slot = GetNextFreeSlot();
147 if (!slot) {
148 // The item does not fit in the cache.
149 return Nothing();
150 }
151 }
152
153 MOZ_ASSERT(slot && CurrentSize() > *slot);
154 return slot;
155 }
156
MarkSlotOccupied(uint16_t aSlotIndex,const wr::WrSpaceAndClipChain & aSpaceAndClip)157 void DisplayItemCache::MarkSlotOccupied(
158 uint16_t aSlotIndex, const wr::WrSpaceAndClipChain& aSpaceAndClip) {
159 // Caching of the item succeeded, update the slot state.
160 auto& slot = mSlots[aSlotIndex];
161 MOZ_ASSERT(!slot.mOccupied);
162 slot.mOccupied = true;
163 MOZ_ASSERT(!slot.mUsed);
164 slot.mUsed = true;
165 slot.mSpaceAndClip = aSpaceAndClip;
166 }
167
CanReuseItem(nsPaintedDisplayItem * aItem,const wr::WrSpaceAndClipChain & aSpaceAndClip)168 Maybe<uint16_t> DisplayItemCache::CanReuseItem(
169 nsPaintedDisplayItem* aItem, const wr::WrSpaceAndClipChain& aSpaceAndClip) {
170 auto& slotIndex = aItem->CacheIndex();
171 if (!slotIndex) {
172 return Nothing();
173 }
174
175 MOZ_ASSERT(slotIndex && CurrentSize() > *slotIndex);
176
177 auto& slot = mSlots[*slotIndex];
178 if (!slot.mOccupied) {
179 // The display item has a stale cache slot. Recache the item.
180 return Nothing();
181 }
182
183 if (!(aSpaceAndClip == slot.mSpaceAndClip)) {
184 // Spatial id and clip id can change between display lists, if items that
185 // generate them change their order.
186 slot.mOccupied = false;
187 aItem->SetCantBeCached();
188 slotIndex = Nothing();
189 } else {
190 slot.mUsed = true;
191 }
192
193 return slotIndex;
194 }
195
196 } // namespace layers
197 } // namespace mozilla
198