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 #ifndef mozilla_image_AnimationFrameBuffer_h
7 #define mozilla_image_AnimationFrameBuffer_h
8 
9 #include "ISurfaceProvider.h"
10 #include <deque>
11 
12 namespace mozilla {
13 namespace image {
14 
15 /**
16  * An AnimationFrameBuffer owns the frames outputted by an animated image
17  * decoder as well as directing its owner on how to drive the decoder,
18  * whether to produce more or to stop.
19  *
20  * This should be subclassed by the different types of queues, depending on
21  * what behaviour is desired.
22  */
23 class AnimationFrameBuffer {
24  public:
25   enum class InsertStatus : uint8_t {
26     YIELD,            // No more frames required at this time.
27     CONTINUE,         // Continue decoding more frames.
28     DISCARD_YIELD,    // Crossed threshold, switch to discarding structure
29                       // and stop decoding more frames.
30     DISCARD_CONTINUE  // Crossed threshold, switch to discarding structure
31                       // and continue decoding more frames.
32   };
33 
34   /**
35    * @param aBatch      Number of frames we request to be decoded each time it
36    *                    decides we need more.
37    *
38    * @param aStartFrame The starting frame for the animation. The frame buffer
39    *                    will auto-advance (and thus keep the decoding pipeline
40    *                    going) until it has reached this frame. Useful when the
41    *                    animation was progressing, but the surface was
42    *                    discarded, and we had to redecode.
43    */
AnimationFrameBuffer(size_t aBatch,size_t aStartFrame)44   AnimationFrameBuffer(size_t aBatch, size_t aStartFrame)
45       : mSize(0),
46         mBatch(aBatch),
47         mGetIndex(0),
48         mAdvance(aStartFrame),
49         mPending(0),
50         mSizeKnown(false),
51         mMayDiscard(false),
52         mRedecodeError(false),
53         mRecycling(false) {
54     if (mBatch > SIZE_MAX / 4) {
55       // Batch size is so big, we will just end up decoding the whole animation.
56       mBatch = SIZE_MAX / 4;
57     } else if (mBatch < 1) {
58       // Never permit a batch size smaller than 1. We always want to be asking
59       // for at least one frame to start.
60       mBatch = 1;
61     }
62   }
63 
AnimationFrameBuffer(const AnimationFrameBuffer & aOther)64   AnimationFrameBuffer(const AnimationFrameBuffer& aOther)
65       : mSize(aOther.mSize),
66         mBatch(aOther.mBatch),
67         mGetIndex(aOther.mGetIndex),
68         mAdvance(aOther.mAdvance),
69         mPending(aOther.mPending),
70         mSizeKnown(aOther.mSizeKnown),
71         mMayDiscard(aOther.mMayDiscard),
72         mRedecodeError(aOther.mRedecodeError),
73         mRecycling(aOther.mRecycling) {}
74 
~AnimationFrameBuffer()75   virtual ~AnimationFrameBuffer() {}
76 
77   /**
78    * @returns True if frames post-advance may be discarded and redecoded on
79    *          demand, else false.
80    */
MayDiscard()81   bool MayDiscard() const { return mMayDiscard; }
82 
83   /**
84    * @returns True if frames post-advance may be reused after displaying, else
85    *          false. Implies MayDiscard().
86    */
IsRecycling()87   bool IsRecycling() const {
88     MOZ_ASSERT_IF(mRecycling, mMayDiscard);
89     return mRecycling;
90   }
91 
92   /**
93    * @returns True if the frame buffer was ever marked as complete. This implies
94    *          that the total number of frames is known and may be gotten from
95    *          Frames().Length().
96    */
SizeKnown()97   bool SizeKnown() const { return mSizeKnown; }
98 
99   /**
100    * @returns The total number of frames in the animation. If SizeKnown() is
101    *          true, then this is a constant, else it is just the total number of
102    *          frames we have decoded thus far.
103    */
Size()104   size_t Size() const { return mSize; }
105 
106   /**
107    * @returns True if encountered an error during redecode which should cause
108    *          the caller to stop inserting frames.
109    */
HasRedecodeError()110   bool HasRedecodeError() const { return mRedecodeError; }
111 
112   /**
113    * @returns The current frame index we have advanced to.
114    */
Displayed()115   size_t Displayed() const { return mGetIndex; }
116 
117   /**
118    * @returns Outstanding frames desired from the decoder.
119    */
PendingDecode()120   size_t PendingDecode() const { return mPending; }
121 
122   /**
123    * @returns Outstanding frames to advance internally.
124    */
PendingAdvance()125   size_t PendingAdvance() const { return mAdvance; }
126 
127   /**
128    * @returns Number of frames we request to be decoded each time it decides we
129    *          need more.
130    */
Batch()131   size_t Batch() const { return mBatch; }
132 
133   /**
134    * Resets the currently displayed frame of the frame buffer to the beginning.
135    *
136    * @returns True if the caller should restart the decoder.
137    */
Reset()138   bool Reset() {
139     mGetIndex = 0;
140     mAdvance = 0;
141     return ResetInternal();
142   }
143 
144   /**
145    * Advance the currently displayed frame of the frame buffer. If it reaches
146    * the end, it will loop back to the beginning. It should not be called unless
147    * a call to Get has returned a valid frame for the next frame index.
148    *
149    * As we advance, the number of frames we have buffered ahead of the current
150    * will shrink. Once that becomes too few, we will request a batch-sized set
151    * of frames to be decoded from the decoder.
152    *
153    * @param aExpectedFrame  The frame we expect to have advanced to. This is
154    *                        used for confirmation purposes (e.g. asserts).
155    *
156    * @returns True if the caller should restart the decoder.
157    */
AdvanceTo(size_t aExpectedFrame)158   bool AdvanceTo(size_t aExpectedFrame) {
159     MOZ_ASSERT(mAdvance == 0);
160 
161     if (++mGetIndex == mSize && mSizeKnown) {
162       mGetIndex = 0;
163     }
164     MOZ_ASSERT(mGetIndex == aExpectedFrame);
165 
166     bool hasPending = mPending > 0;
167     AdvanceInternal();
168     // Restart the decoder if we transitioned from no pending frames being
169     // decoded, to some pending frames to be decoded.
170     return !hasPending && mPending > 0;
171   }
172 
173   /**
174    * Inserts a frame into the frame buffer.
175    *
176    * Once we have a sufficient number of frames buffered relative to the
177    * currently displayed frame, it will return YIELD to indicate the caller
178    * should stop decoding. Otherwise it will return CONTINUE.
179    *
180    * If we cross the threshold, it will return DISCARD_YIELD or DISCARD_CONTINUE
181    * to indicate that the caller should switch to a new queue type.
182    *
183    * @param aFrame      The frame to insert into the buffer.
184    *
185    * @returns True if the decoder should decode another frame.
186    */
Insert(RefPtr<imgFrame> && aFrame)187   InsertStatus Insert(RefPtr<imgFrame>&& aFrame) {
188     MOZ_ASSERT(mPending > 0);
189     MOZ_ASSERT(aFrame);
190 
191     --mPending;
192     bool retain = InsertInternal(std::move(aFrame));
193 
194     if (mAdvance > 0 && mSize > 1) {
195       --mAdvance;
196       ++mGetIndex;
197       AdvanceInternal();
198     }
199 
200     if (!retain) {
201       return mPending > 0 ? InsertStatus::DISCARD_CONTINUE
202                           : InsertStatus::DISCARD_YIELD;
203     }
204 
205     return mPending > 0 ? InsertStatus::CONTINUE : InsertStatus::YIELD;
206   }
207 
208   /**
209    * Access a specific frame from the frame buffer. It should generally access
210    * frames in sequential order, increasing in tandem with AdvanceTo calls. The
211    * first frame may be accessed at any time. The access order should start with
212    * the same value as that given in Initialize (aStartFrame).
213    *
214    * @param aFrame      The frame index to access.
215    *
216    * @returns The frame, if available.
217    */
218   virtual imgFrame* Get(size_t aFrame, bool aForDisplay) = 0;
219 
220   /**
221    * @returns True if the first frame of the animation (not of the queue) is
222    *          available/finished, else false.
223    */
224   virtual bool IsFirstFrameFinished() const = 0;
225 
226   /**
227    * @returns True if the last inserted frame matches the given frame, else
228    *          false.
229    */
230   virtual bool IsLastInsertedFrame(imgFrame* aFrame) const = 0;
231 
232   /**
233    * This should be called after the last frame has been inserted. If the buffer
234    * is discarding old frames, it may request more frames to be decoded. In this
235    * case that means the decoder should start again from the beginning. This
236    * return value should be used in preference to that of the Insert call.
237    *
238    * @returns True if the decoder should decode another frame.
239    */
240   virtual bool MarkComplete(const gfx::IntRect& aFirstFrameRefreshArea) = 0;
241 
242   typedef ISurfaceProvider::AddSizeOfCbData AddSizeOfCbData;
243   typedef ISurfaceProvider::AddSizeOfCb AddSizeOfCb;
244 
245   /**
246    * Accumulate the total cost of all the frames in the buffer.
247    */
248   virtual void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
249                                       const AddSizeOfCb& aCallback) = 0;
250 
251   /**
252    * Request a recycled frame buffer, and if available, set aRecycleRect to be
253    * the dirty rect between the contents of the recycled frame, and the restore
254    * frame (e.g. what we composite on top of) for the next frame to be created.
255    *
256    * @returns The frame to be recycled, if available.
257    */
RecycleFrame(gfx::IntRect & aRecycleRect)258   virtual RawAccessFrameRef RecycleFrame(gfx::IntRect& aRecycleRect) {
259     MOZ_ASSERT(!mRecycling);
260     return RawAccessFrameRef();
261   }
262 
263  protected:
264   /**
265    * Perform the actual insertion of the given frame into the underlying buffer
266    * representation. mGetIndex shall be the index of the frame we are inserting,
267    * and mSize and mPending have already been adjusted as needed.
268    *
269    * @returns True if the caller should continue as normal, false if the discard
270    *          threshold was crossed and we should change queue types.
271    */
272   virtual bool InsertInternal(RefPtr<imgFrame>&& aFrame) = 0;
273 
274   /**
275    * Advance from the current frame to the immediately adjacent next frame.
276    * mGetIndex shall be the the index of the new current frame after advancing.
277    * mPending may be adjusted to request more frames.
278    */
279   virtual void AdvanceInternal() = 0;
280 
281   /**
282    * Discard any frames as necessary for the reset. mPending may be adjusted to
283    * request more frames.
284    *
285    * @returns True if the caller should resume decoding new frames, else false.
286    */
287   virtual bool ResetInternal() = 0;
288 
289   // The total number of frames in the animation. If mSizeKnown is true, it is
290   // the actual total regardless of how many frames are available, otherwise it
291   // is the total number of inserted frames.
292   size_t mSize;
293 
294   // The minimum number of frames that we want buffered ahead of the display.
295   size_t mBatch;
296 
297   // The sequential index of the frame we have advanced to.
298   size_t mGetIndex;
299 
300   // The number of frames we need to auto-advance to synchronize with the
301   // caller.
302   size_t mAdvance;
303 
304   // The number of frames to decode before we stop.
305   size_t mPending;
306 
307   // True if the total number of frames for the animation is known.
308   bool mSizeKnown;
309 
310   // True if this buffer may discard frames.
311   bool mMayDiscard;
312 
313   // True if we encountered an error while redecoding.
314   bool mRedecodeError;
315 
316   // True if this buffer is recycling frames.
317   bool mRecycling;
318 };
319 
320 /**
321  * An AnimationFrameRetainedBuffer will retain all of the frames inserted into
322  * it. Once it crosses its maximum number of frames, it will recommend
323  * conversion to a discarding queue.
324  */
325 class AnimationFrameRetainedBuffer final : public AnimationFrameBuffer {
326  public:
327   /**
328    * @param aThreshold  Maximum number of frames that may be stored in the frame
329    *                    buffer before it may discard already displayed frames.
330    *                    Once exceeded, it will discard the previous frame to the
331    *                    current frame whenever Advance is called. It always
332    *                    retains the first frame.
333    *
334    * @param aBatch      See AnimationFrameBuffer::AnimationFrameBuffer.
335    *
336    * @param aStartFrame See AnimationFrameBuffer::AnimationFrameBuffer.
337    */
338   AnimationFrameRetainedBuffer(size_t aThreshold, size_t aBatch,
339                                size_t aCurrentFrame);
340 
341   /**
342    * @returns Maximum number of frames before we start discarding previous
343    *          frames post-advance.
344    */
Threshold()345   size_t Threshold() const { return mThreshold; }
346 
347   /**
348    * @returns The frames of this animation, in order. Each element will always
349    *          contain a valid frame.
350    */
Frames()351   const nsTArray<RefPtr<imgFrame>>& Frames() const { return mFrames; }
352 
353   imgFrame* Get(size_t aFrame, bool aForDisplay) override;
354   bool IsFirstFrameFinished() const override;
355   bool IsLastInsertedFrame(imgFrame* aFrame) const override;
356   bool MarkComplete(const gfx::IntRect& aFirstFrameRefreshArea) override;
357   void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
358                               const AddSizeOfCb& aCallback) override;
359 
360  private:
361   friend class AnimationFrameDiscardingQueue;
362   friend class AnimationFrameRecyclingQueue;
363 
364   bool InsertInternal(RefPtr<imgFrame>&& aFrame) override;
365   void AdvanceInternal() override;
366   bool ResetInternal() override;
367 
368   // The frames of this animation, in order.
369   nsTArray<RefPtr<imgFrame>> mFrames;
370 
371   // The maximum number of frames we can have before discarding.
372   size_t mThreshold;
373 };
374 
375 /**
376  * An AnimationFrameDiscardingQueue will only retain up to mBatch * 2 frames.
377  * When the animation advances, it will discard the old current frame.
378  */
379 class AnimationFrameDiscardingQueue : public AnimationFrameBuffer {
380  public:
381   explicit AnimationFrameDiscardingQueue(AnimationFrameRetainedBuffer&& aQueue);
382 
383   imgFrame* Get(size_t aFrame, bool aForDisplay) final;
384   bool IsFirstFrameFinished() const final;
385   bool IsLastInsertedFrame(imgFrame* aFrame) const final;
386   bool MarkComplete(const gfx::IntRect& aFirstFrameRefreshArea) override;
387   void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
388                               const AddSizeOfCb& aCallback) override;
389 
Display()390   const std::deque<RefPtr<imgFrame>>& Display() const { return mDisplay; }
FirstFrame()391   const imgFrame* FirstFrame() const { return mFirstFrame; }
PendingInsert()392   size_t PendingInsert() const { return mInsertIndex; }
393 
394  protected:
395   bool InsertInternal(RefPtr<imgFrame>&& aFrame) override;
396   void AdvanceInternal() override;
397   bool ResetInternal() override;
398 
399   /// The sequential index of the frame we inserting next.
400   size_t mInsertIndex;
401 
402   /// Queue storing frames to be displayed by the animator. The first frame in
403   /// the queue is the currently displayed frame.
404   std::deque<RefPtr<imgFrame>> mDisplay;
405 
406   /// The first frame which is never discarded, and preferentially reused.
407   RefPtr<imgFrame> mFirstFrame;
408 };
409 
410 /**
411  * An AnimationFrameRecyclingQueue will only retain up to mBatch * 2 frames.
412  * When the animation advances, it will place the old current frame into a
413  * recycling queue to be reused for a future allocation. This only works for
414  * animated images where we decoded full sized frames into their own buffers,
415  * so that the buffers are all identically sized and contain the complete frame
416  * data.
417  */
418 class AnimationFrameRecyclingQueue final
419     : public AnimationFrameDiscardingQueue {
420  public:
421   explicit AnimationFrameRecyclingQueue(AnimationFrameRetainedBuffer&& aQueue);
422 
423   bool MarkComplete(const gfx::IntRect& aFirstFrameRefreshArea) override;
424   void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
425                               const AddSizeOfCb& aCallback) override;
426 
427   RawAccessFrameRef RecycleFrame(gfx::IntRect& aRecycleRect) override;
428 
429   struct RecycleEntry {
RecycleEntryRecycleEntry430     explicit RecycleEntry(const gfx::IntRect& aDirtyRect)
431         : mDirtyRect(aDirtyRect) {}
432 
RecycleEntryRecycleEntry433     RecycleEntry(RecycleEntry&& aOther)
434         : mFrame(std::move(aOther.mFrame)), mDirtyRect(aOther.mDirtyRect) {}
435 
436     RecycleEntry& operator=(RecycleEntry&& aOther) {
437       mFrame = std::move(aOther.mFrame);
438       mDirtyRect = aOther.mDirtyRect;
439       return *this;
440     }
441 
442     RecycleEntry(const RecycleEntry& aOther) = delete;
443     RecycleEntry& operator=(const RecycleEntry& aOther) = delete;
444 
445     RefPtr<imgFrame> mFrame;  // The frame containing the buffer to recycle.
446     gfx::IntRect mDirtyRect;  // The dirty rect of the frame itself.
447   };
448 
Recycle()449   const std::deque<RecycleEntry>& Recycle() const { return mRecycle; }
FirstFrameRefreshArea()450   const gfx::IntRect& FirstFrameRefreshArea() const {
451     return mFirstFrameRefreshArea;
452   }
453 
454  protected:
455   void AdvanceInternal() override;
456   bool ResetInternal() override;
457 
458   /// Queue storing frames to be recycled by the decoder to produce its future
459   /// frames. May contain up to mBatch frames, where the last frame in the queue
460   /// is adjacent to the first frame in the mDisplay queue.
461   std::deque<RecycleEntry> mRecycle;
462 
463   /// The first frame refresh area. This is used instead of the dirty rect for
464   /// the last frame when transitioning back to the first frame.
465   gfx::IntRect mFirstFrameRefreshArea;
466 
467   /// Force recycled frames to use the first frame refresh area as their dirty
468   /// rect. This is used when we are recycling frames from the end of an
469   /// animation to produce frames at the beginning of an animation.
470   bool mForceUseFirstFrameRefreshArea;
471 };
472 
473 }  // namespace image
474 }  // namespace mozilla
475 
476 #endif  // mozilla_image_AnimationFrameBuffer_h
477