1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "AnimationFrameBuffer.h"
7 
8 #include <utility>  // for Move
9 
10 namespace mozilla {
11 namespace image {
12 
AnimationFrameRetainedBuffer(size_t aThreshold,size_t aBatch,size_t aStartFrame)13 AnimationFrameRetainedBuffer::AnimationFrameRetainedBuffer(size_t aThreshold,
14                                                            size_t aBatch,
15                                                            size_t aStartFrame)
16     : AnimationFrameBuffer(aBatch, aStartFrame), mThreshold(aThreshold) {
17   // To simplify the code, we have the assumption that the threshold for
18   // entering discard-after-display mode is at least twice the batch size (since
19   // that is the most frames-pending-decode we will request) + 1 for the current
20   // frame. That way the redecoded frames being inserted will never risk
21   // overlapping the frames we will discard due to the animation progressing.
22   // That may cause us to use a little more memory than we want but that is an
23   // acceptable tradeoff for simplicity.
24   size_t minThreshold = 2 * mBatch + 1;
25   if (mThreshold < minThreshold) {
26     mThreshold = minThreshold;
27   }
28 
29   // The maximum number of frames we should ever have decoded at one time is
30   // twice the batch. That is a good as number as any to start our decoding at.
31   mPending = mBatch * 2;
32 }
33 
InsertInternal(RefPtr<imgFrame> && aFrame)34 bool AnimationFrameRetainedBuffer::InsertInternal(RefPtr<imgFrame>&& aFrame) {
35   // We should only insert new frames if we actually asked for them.
36   MOZ_ASSERT(!mSizeKnown);
37   MOZ_ASSERT(mFrames.Length() < mThreshold);
38 
39   ++mSize;
40   mFrames.AppendElement(std::move(aFrame));
41   MOZ_ASSERT(mSize == mFrames.Length());
42   return mSize < mThreshold;
43 }
44 
ResetInternal()45 bool AnimationFrameRetainedBuffer::ResetInternal() {
46   // If we haven't crossed the threshold, then we know by definition we have
47   // not discarded any frames. If we previously requested more frames, but
48   // it would have been more than we would have buffered otherwise, we can
49   // stop the decoding after one more frame.
50   if (mPending > 1 && mSize >= mBatch * 2 + 1) {
51     MOZ_ASSERT(!mSizeKnown);
52     mPending = 1;
53   }
54 
55   // Either the decoder is still running, or we have enough frames already.
56   // No need for us to restart it.
57   return false;
58 }
59 
MarkComplete(const gfx::IntRect & aFirstFrameRefreshArea)60 bool AnimationFrameRetainedBuffer::MarkComplete(
61     const gfx::IntRect& aFirstFrameRefreshArea) {
62   MOZ_ASSERT(!mSizeKnown);
63   mFirstFrameRefreshArea = aFirstFrameRefreshArea;
64   mSizeKnown = true;
65   mPending = 0;
66   mFrames.Compact();
67   return false;
68 }
69 
AdvanceInternal()70 void AnimationFrameRetainedBuffer::AdvanceInternal() {
71   // We should not have advanced if we never inserted.
72   MOZ_ASSERT(!mFrames.IsEmpty());
73   // We only want to change the current frame index if we have advanced. This
74   // means either a higher frame index, or going back to the beginning.
75   size_t framesLength = mFrames.Length();
76   // We should never have advanced beyond the frame buffer.
77   MOZ_ASSERT(mGetIndex < framesLength);
78   // We should never advance if the current frame is null -- it needs to know
79   // the timeout from it at least to know when to advance.
80   MOZ_ASSERT_IF(mGetIndex > 0, mFrames[mGetIndex - 1]);
81   MOZ_ASSERT_IF(mGetIndex == 0, mFrames[framesLength - 1]);
82   // The owner should have already accessed the next frame, so it should also
83   // be available.
84   MOZ_ASSERT(mFrames[mGetIndex]);
85 
86   if (!mSizeKnown) {
87     // Calculate how many frames we have requested ahead of the current frame.
88     size_t buffered = mPending + framesLength - mGetIndex - 1;
89     if (buffered < mBatch) {
90       // If we have fewer frames than the batch size, then ask for more. If we
91       // do not have any pending, then we know that there is no active decoding.
92       mPending += mBatch;
93     }
94   }
95 }
96 
Get(size_t aFrame,bool aForDisplay)97 imgFrame* AnimationFrameRetainedBuffer::Get(size_t aFrame, bool aForDisplay) {
98   // We should not have asked for a frame if we never inserted.
99   if (mFrames.IsEmpty()) {
100     MOZ_ASSERT_UNREACHABLE("Calling Get() when we have no frames");
101     return nullptr;
102   }
103 
104   // If we don't have that frame, return an empty frame ref.
105   if (aFrame >= mFrames.Length()) {
106     return nullptr;
107   }
108 
109   // If we have space for the frame, it should always be available.
110   if (!mFrames[aFrame]) {
111     MOZ_ASSERT_UNREACHABLE("Calling Get() when frame is unavailable");
112     return nullptr;
113   }
114 
115   // If we are advancing on behalf of the animation, we don't expect it to be
116   // getting any frames (besides the first) until we get the desired frame.
117   MOZ_ASSERT(aFrame == 0 || mAdvance == 0);
118   return mFrames[aFrame].get();
119 }
120 
IsFirstFrameFinished() const121 bool AnimationFrameRetainedBuffer::IsFirstFrameFinished() const {
122   return !mFrames.IsEmpty() && mFrames[0]->IsFinished();
123 }
124 
IsLastInsertedFrame(imgFrame * aFrame) const125 bool AnimationFrameRetainedBuffer::IsLastInsertedFrame(imgFrame* aFrame) const {
126   return !mFrames.IsEmpty() && mFrames.LastElement().get() == aFrame;
127 }
128 
AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,const AddSizeOfCb & aCallback)129 void AnimationFrameRetainedBuffer::AddSizeOfExcludingThis(
130     MallocSizeOf aMallocSizeOf, const AddSizeOfCb& aCallback) {
131   size_t i = 0;
132   for (const RefPtr<imgFrame>& frame : mFrames) {
133     ++i;
134     frame->AddSizeOfExcludingThis(aMallocSizeOf,
135                                   [&](AddSizeOfCbData& aMetadata) {
136                                     aMetadata.mIndex = i;
137                                     aCallback(aMetadata);
138                                   });
139   }
140 }
141 
AnimationFrameDiscardingQueue(AnimationFrameRetainedBuffer && aQueue)142 AnimationFrameDiscardingQueue::AnimationFrameDiscardingQueue(
143     AnimationFrameRetainedBuffer&& aQueue)
144     : AnimationFrameBuffer(aQueue),
145       mInsertIndex(aQueue.mFrames.Length()),
146       mFirstFrame(aQueue.mFrames[0]) {
147   MOZ_ASSERT(!mSizeKnown);
148   MOZ_ASSERT(!mRedecodeError);
149   MOZ_ASSERT(mInsertIndex > 0);
150   mMayDiscard = true;
151 
152   // We avoided moving aQueue.mFrames[0] for mFirstFrame above because it is
153   // possible the animation was reset back to the beginning, and then we crossed
154   // the threshold without advancing further. That would mean mGetIndex is 0.
155   for (size_t i = mGetIndex; i < mInsertIndex; ++i) {
156     MOZ_ASSERT(aQueue.mFrames[i]);
157     mDisplay.push_back(std::move(aQueue.mFrames[i]));
158   }
159 }
160 
InsertInternal(RefPtr<imgFrame> && aFrame)161 bool AnimationFrameDiscardingQueue::InsertInternal(RefPtr<imgFrame>&& aFrame) {
162   if (mInsertIndex == mSize) {
163     if (mSizeKnown) {
164       // We produced more frames on a subsequent decode than on the first pass.
165       mRedecodeError = true;
166       mPending = 0;
167       return true;
168     }
169     ++mSize;
170   }
171 
172   // Even though we don't use redecoded first frames for display purposes, we
173   // will still use them for recycling, so we still need to insert it.
174   mDisplay.push_back(std::move(aFrame));
175   ++mInsertIndex;
176   MOZ_ASSERT(mInsertIndex <= mSize);
177   return true;
178 }
179 
ResetInternal()180 bool AnimationFrameDiscardingQueue::ResetInternal() {
181   mDisplay.clear();
182   mInsertIndex = 0;
183 
184   bool restartDecoder = mPending == 0;
185   mPending = 2 * mBatch;
186   return restartDecoder;
187 }
188 
MarkComplete(const gfx::IntRect & aFirstFrameRefreshArea)189 bool AnimationFrameDiscardingQueue::MarkComplete(
190     const gfx::IntRect& aFirstFrameRefreshArea) {
191   if (NS_WARN_IF(mInsertIndex != mSize)) {
192     mRedecodeError = true;
193     mPending = 0;
194   }
195 
196   // If we encounter a redecode error, just make the first frame refresh area to
197   // be the full frame, because we don't really know what we can safely recycle.
198   mFirstFrameRefreshArea =
199       mRedecodeError ? mFirstFrame->GetRect() : aFirstFrameRefreshArea;
200 
201   // We reached the end of the animation, the next frame we get, if we get
202   // another, will be the first frame again.
203   mInsertIndex = 0;
204   mSizeKnown = true;
205 
206   // Since we only request advancing when we want to resume at a certain point
207   // in the animation, we should never exceed the number of frames.
208   MOZ_ASSERT(mAdvance == 0);
209   return mPending > 0;
210 }
211 
AdvanceInternal()212 void AnimationFrameDiscardingQueue::AdvanceInternal() {
213   // We only want to change the current frame index if we have advanced. This
214   // means either a higher frame index, or going back to the beginning.
215   // We should never have advanced beyond the frame buffer.
216   MOZ_ASSERT(mGetIndex < mSize);
217 
218   // We should have the current frame still in the display queue. Either way,
219   // we should at least have an entry in the queue which we need to consume.
220   MOZ_ASSERT(!mDisplay.empty());
221   MOZ_ASSERT(mDisplay.front());
222   mDisplay.pop_front();
223   MOZ_ASSERT(!mDisplay.empty());
224   MOZ_ASSERT(mDisplay.front());
225 
226   if (mDisplay.size() + mPending - 1 < mBatch) {
227     // If we have fewer frames than the batch size, then ask for more. If we
228     // do not have any pending, then we know that there is no active decoding.
229     mPending += mBatch;
230   }
231 }
232 
Get(size_t aFrame,bool aForDisplay)233 imgFrame* AnimationFrameDiscardingQueue::Get(size_t aFrame, bool aForDisplay) {
234   // The first frame is stored separately. If we only need the frame for
235   // display purposes, we can return it right away. If we need it for advancing
236   // the animation, we want to verify the recreated first frame is available
237   // before allowing it continue.
238   if (aForDisplay && aFrame == 0) {
239     return mFirstFrame.get();
240   }
241 
242   // If we don't have that frame, return an empty frame ref.
243   if (aFrame >= mSize) {
244     return nullptr;
245   }
246 
247   size_t offset;
248   if (aFrame >= mGetIndex) {
249     offset = aFrame - mGetIndex;
250   } else if (!mSizeKnown) {
251     MOZ_ASSERT_UNREACHABLE("Requesting previous frame after we have advanced!");
252     return nullptr;
253   } else {
254     offset = mSize - mGetIndex + aFrame;
255   }
256 
257   if (offset >= mDisplay.size()) {
258     return nullptr;
259   }
260 
261   // If we are advancing on behalf of the animation, we don't expect it to be
262   // getting any frames (besides the first) until we get the desired frame.
263   MOZ_ASSERT(aFrame == 0 || mAdvance == 0);
264 
265   // If we have space for the frame, it should always be available.
266   MOZ_ASSERT(mDisplay[offset]);
267   return mDisplay[offset].get();
268 }
269 
IsFirstFrameFinished() const270 bool AnimationFrameDiscardingQueue::IsFirstFrameFinished() const {
271   MOZ_ASSERT(mFirstFrame);
272   MOZ_ASSERT(mFirstFrame->IsFinished());
273   return true;
274 }
275 
IsLastInsertedFrame(imgFrame * aFrame) const276 bool AnimationFrameDiscardingQueue::IsLastInsertedFrame(
277     imgFrame* aFrame) const {
278   return !mDisplay.empty() && mDisplay.back().get() == aFrame;
279 }
280 
AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,const AddSizeOfCb & aCallback)281 void AnimationFrameDiscardingQueue::AddSizeOfExcludingThis(
282     MallocSizeOf aMallocSizeOf, const AddSizeOfCb& aCallback) {
283   mFirstFrame->AddSizeOfExcludingThis(aMallocSizeOf,
284                                       [&](AddSizeOfCbData& aMetadata) {
285                                         aMetadata.mIndex = 1;
286                                         aCallback(aMetadata);
287                                       });
288 
289   size_t i = mGetIndex;
290   for (const RefPtr<imgFrame>& frame : mDisplay) {
291     ++i;
292     if (mSize < i) {
293       i = 1;
294       if (mFirstFrame.get() == frame.get()) {
295         // First frame again, we already covered it above. We can have a
296         // different frame in the first frame position in the discard queue
297         // on subsequent passes of the animation. This is useful for recycling.
298         continue;
299       }
300     }
301 
302     frame->AddSizeOfExcludingThis(aMallocSizeOf,
303                                   [&](AddSizeOfCbData& aMetadata) {
304                                     aMetadata.mIndex = i;
305                                     aCallback(aMetadata);
306                                   });
307   }
308 }
309 
AnimationFrameRecyclingQueue(AnimationFrameRetainedBuffer && aQueue)310 AnimationFrameRecyclingQueue::AnimationFrameRecyclingQueue(
311     AnimationFrameRetainedBuffer&& aQueue)
312     : AnimationFrameDiscardingQueue(std::move(aQueue)),
313       mForceUseFirstFrameRefreshArea(false) {
314   // In an ideal world, we would always save the already displayed frames for
315   // recycling but none of the frames were marked as recyclable. We will incur
316   // the extra allocation cost for a few more frames.
317   mRecycling = true;
318 
319   // Until we reach the end of the animation, set the first frame refresh area
320   // to match that of the full area of the first frame.
321   mFirstFrameRefreshArea = mFirstFrame->GetRect();
322 }
323 
AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,const AddSizeOfCb & aCallback)324 void AnimationFrameRecyclingQueue::AddSizeOfExcludingThis(
325     MallocSizeOf aMallocSizeOf, const AddSizeOfCb& aCallback) {
326   AnimationFrameDiscardingQueue::AddSizeOfExcludingThis(aMallocSizeOf,
327                                                         aCallback);
328 
329   for (const RecycleEntry& entry : mRecycle) {
330     if (entry.mFrame) {
331       entry.mFrame->AddSizeOfExcludingThis(
332           aMallocSizeOf, [&](AddSizeOfCbData& aMetadata) {
333             aMetadata.mIndex = 0;  // Frame is not applicable
334             aCallback(aMetadata);
335           });
336     }
337   }
338 }
339 
AdvanceInternal()340 void AnimationFrameRecyclingQueue::AdvanceInternal() {
341   // We only want to change the current frame index if we have advanced. This
342   // means either a higher frame index, or going back to the beginning.
343   // We should never have advanced beyond the frame buffer.
344   MOZ_ASSERT(mGetIndex < mSize);
345 
346   MOZ_ASSERT(!mDisplay.empty());
347   MOZ_ASSERT(mDisplay.front());
348 
349   // We have advanced past the first frame. That means the next frame we are
350   // putting in the queue to recycling is the first frame in the animation,
351   // and we no longer need to worry about having looped around.
352   if (mGetIndex == 1) {
353     mForceUseFirstFrameRefreshArea = false;
354   }
355 
356   RefPtr<imgFrame>& front = mDisplay.front();
357   RecycleEntry newEntry(mForceUseFirstFrameRefreshArea ? mFirstFrameRefreshArea
358                                                        : front->GetDirtyRect());
359 
360   // If we are allowed to recycle the frame, then we should save it before the
361   // base class's AdvanceInternal discards it.
362   newEntry.mFrame = std::move(front);
363 
364   // Even if the frame itself isn't saved, we want the dirty rect to calculate
365   // the recycle rect for future recycled frames.
366   mRecycle.push_back(std::move(newEntry));
367   mDisplay.pop_front();
368   MOZ_ASSERT(!mDisplay.empty());
369   MOZ_ASSERT(mDisplay.front());
370 
371   if (mDisplay.size() + mPending - 1 < mBatch) {
372     // If we have fewer frames than the batch size, then ask for more. If we
373     // do not have any pending, then we know that there is no active decoding.
374     //
375     // We limit the batch to avoid using the frame we just added to the queue.
376     // This gives other parts of the system time to switch to the new current
377     // frame, and maximize buffer reuse. In particular this is useful for
378     // WebRender which holds onto the previous frame for much longer.
379     size_t newPending = std::min(mPending + mBatch, mRecycle.size() - 1);
380     if (newPending == 0 && (mDisplay.size() <= 1 || mPending > 0)) {
381       // If we already have pending frames, then the decoder is active and we
382       // cannot go below one. If we are displaying the only frame we have, and
383       // there are none pending, then we must request at least one more frame to
384       // continue to animation, because we won't advance again without a new
385       // frame. This may cause us to skip recycling because the previous frame
386       // is still in use.
387       newPending = 1;
388     }
389     mPending = newPending;
390   }
391 }
392 
ResetInternal()393 bool AnimationFrameRecyclingQueue::ResetInternal() {
394   // We should save any display frames that we can to save on at least the
395   // allocation. The first frame refresh area is guaranteed to be the aggregate
396   // dirty rect or the entire frame, and so the bare minimum area we can
397   // recycle. We don't need to worry about updating the dirty rect for the
398   // existing mRecycle entries, because that will happen in RecycleFrame when
399   // we try to pull out a frame to redecode the first frame.
400   for (RefPtr<imgFrame>& frame : mDisplay) {
401     RecycleEntry newEntry(mFirstFrameRefreshArea);
402     newEntry.mFrame = std::move(frame);
403     mRecycle.push_back(std::move(newEntry));
404   }
405 
406   return AnimationFrameDiscardingQueue::ResetInternal();
407 }
408 
RecycleFrame(gfx::IntRect & aRecycleRect)409 RawAccessFrameRef AnimationFrameRecyclingQueue::RecycleFrame(
410     gfx::IntRect& aRecycleRect) {
411   if (mInsertIndex == 0) {
412     // If we are recreating the first frame, then we actually have already
413     // precomputed aggregate of the dirty rects as the first frame refresh
414     // area. We know that all of the frames still in the recycling queue
415     // need to take into account the same dirty rect because they are also
416     // frames which cross the boundary.
417     //
418     // Note that this may actually shrink the dirty rect if we estimated it
419     // earlier with the full frame size and now we have the actual, more
420     // conservative aggregate for the animation.
421     for (RecycleEntry& entry : mRecycle) {
422       entry.mDirtyRect = mFirstFrameRefreshArea;
423     }
424     // Until we advance to the first frame again, any subsequent recycled
425     // frames should also use the first frame refresh area.
426     mForceUseFirstFrameRefreshArea = true;
427   }
428 
429   if (mRecycle.empty()) {
430     return RawAccessFrameRef();
431   }
432 
433   RawAccessFrameRef recycledFrame;
434   if (mRecycle.front().mFrame) {
435     recycledFrame = mRecycle.front().mFrame->RawAccessRef();
436     MOZ_ASSERT(recycledFrame);
437     mRecycle.pop_front();
438 
439     if (mForceUseFirstFrameRefreshArea) {
440       // We are still crossing the loop boundary and cannot rely upon the dirty
441       // rects of entries in mDisplay to be representative. E.g. The first frame
442       // is probably has a full frame dirty rect.
443       aRecycleRect = mFirstFrameRefreshArea;
444     } else {
445       // Calculate the recycle rect for the recycled frame. This is the
446       // cumulative dirty rect of all of the frames ahead of us to be displayed,
447       // and to be used for recycling. Or in other words, the dirty rect between
448       // the recycled frame and the decoded frame which reuses the buffer.
449       //
450       // We know at this point that mRecycle contains either frames from the end
451       // of the animation with the first frame refresh area as the dirty rect
452       // (plus the first frame likewise) and frames with their actual dirty rect
453       // from the start. mDisplay should also only contain frames from the start
454       // of the animation onwards.
455       aRecycleRect.SetRect(0, 0, 0, 0);
456       for (const RefPtr<imgFrame>& frame : mDisplay) {
457         aRecycleRect = aRecycleRect.Union(frame->GetDirtyRect());
458       }
459       for (const RecycleEntry& entry : mRecycle) {
460         aRecycleRect = aRecycleRect.Union(entry.mDirtyRect);
461       }
462     }
463   } else {
464     mRecycle.pop_front();
465   }
466 
467   return recycledFrame;
468 }
469 
470 }  // namespace image
471 }  // namespace mozilla
472