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