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/. */
6 #include "AnimationFrameBuffer.h"
8 #include <utility> // for Move
13 AnimationFrameRetainedBuffer::AnimationFrameRetainedBuffer(size_t aThreshold
,
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
;
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;
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
);
40 mFrames
.AppendElement(std::move(aFrame
));
41 MOZ_ASSERT(mSize
== mFrames
.Length());
42 return mSize
< mThreshold
;
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
);
55 // Either the decoder is still running, or we have enough frames already.
56 // No need for us to restart it.
60 bool AnimationFrameRetainedBuffer::MarkComplete(
61 const gfx::IntRect
& aFirstFrameRefreshArea
) {
62 MOZ_ASSERT(!mSizeKnown
);
63 mFirstFrameRefreshArea
= aFirstFrameRefreshArea
;
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
84 MOZ_ASSERT(mFrames
[mGetIndex
]);
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.
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");
104 // If we don't have that frame, return an empty frame ref.
105 if (aFrame
>= mFrames
.Length()) {
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");
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();
121 bool AnimationFrameRetainedBuffer::IsFirstFrameFinished() const {
122 return !mFrames
.IsEmpty() && mFrames
[0]->IsFinished();
125 bool AnimationFrameRetainedBuffer::IsLastInsertedFrame(imgFrame
* aFrame
) const {
126 return !mFrames
.IsEmpty() && mFrames
.LastElement().get() == aFrame
;
129 void AnimationFrameRetainedBuffer::AddSizeOfExcludingThis(
130 MallocSizeOf aMallocSizeOf
, const AddSizeOfCb
& aCallback
) {
132 for (const RefPtr
<imgFrame
>& frame
: mFrames
) {
134 frame
->AddSizeOfExcludingThis(aMallocSizeOf
,
135 [&](AddSizeOfCbData
& aMetadata
) {
136 aMetadata
.mIndex
= i
;
137 aCallback(aMetadata
);
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);
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
]));
161 bool AnimationFrameDiscardingQueue::InsertInternal(RefPtr
<imgFrame
>&& aFrame
) {
162 if (mInsertIndex
== mSize
) {
164 // We produced more frames on a subsequent decode than on the first pass.
165 mRedecodeError
= true;
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
));
176 MOZ_ASSERT(mInsertIndex
<= mSize
);
180 bool AnimationFrameDiscardingQueue::ResetInternal() {
184 bool restartDecoder
= mPending
== 0;
185 mPending
= 2 * mBatch
;
186 return restartDecoder
;
189 bool AnimationFrameDiscardingQueue::MarkComplete(
190 const gfx::IntRect
& aFirstFrameRefreshArea
) {
191 if (NS_WARN_IF(mInsertIndex
!= mSize
)) {
192 mRedecodeError
= true;
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
;
201 // We reached the end of the animation, the next frame we get, if we get
202 // another, will be the first frame again.
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);
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
);
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());
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.
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();
242 // If we don't have that frame, return an empty frame ref.
243 if (aFrame
>= mSize
) {
248 if (aFrame
>= mGetIndex
) {
249 offset
= aFrame
- mGetIndex
;
250 } else if (!mSizeKnown
) {
251 MOZ_ASSERT_UNREACHABLE("Requesting previous frame after we have advanced!");
254 offset
= mSize
- mGetIndex
+ aFrame
;
257 if (offset
>= mDisplay
.size()) {
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);
265 // If we have space for the frame, it should always be available.
266 MOZ_ASSERT(mDisplay
[offset
]);
267 return mDisplay
[offset
].get();
270 bool AnimationFrameDiscardingQueue::IsFirstFrameFinished() const {
271 MOZ_ASSERT(mFirstFrame
);
272 MOZ_ASSERT(mFirstFrame
->IsFinished());
276 bool AnimationFrameDiscardingQueue::IsLastInsertedFrame(
277 imgFrame
* aFrame
) const {
278 return !mDisplay
.empty() && mDisplay
.back().get() == aFrame
;
281 void AnimationFrameDiscardingQueue::AddSizeOfExcludingThis(
282 MallocSizeOf aMallocSizeOf
, const AddSizeOfCb
& aCallback
) {
283 mFirstFrame
->AddSizeOfExcludingThis(aMallocSizeOf
,
284 [&](AddSizeOfCbData
& aMetadata
) {
285 aMetadata
.mIndex
= 1;
286 aCallback(aMetadata
);
289 size_t i
= mGetIndex
;
290 for (const RefPtr
<imgFrame
>& frame
: mDisplay
) {
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.
302 frame
->AddSizeOfExcludingThis(aMallocSizeOf
,
303 [&](AddSizeOfCbData
& aMetadata
) {
304 aMetadata
.mIndex
= i
;
305 aCallback(aMetadata
);
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.
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();
324 void AnimationFrameRecyclingQueue::AddSizeOfExcludingThis(
325 MallocSizeOf aMallocSizeOf
, const AddSizeOfCb
& aCallback
) {
326 AnimationFrameDiscardingQueue::AddSizeOfExcludingThis(aMallocSizeOf
,
329 for (const RecycleEntry
& entry
: mRecycle
) {
331 entry
.mFrame
->AddSizeOfExcludingThis(
332 aMallocSizeOf
, [&](AddSizeOfCbData
& aMetadata
) {
333 aMetadata
.mIndex
= 0; // Frame is not applicable
334 aCallback(aMetadata
);
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
);
346 MOZ_ASSERT(!mDisplay
.empty());
347 MOZ_ASSERT(mDisplay
.front());
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;
356 RefPtr
<imgFrame
>& front
= mDisplay
.front();
357 RecycleEntry
newEntry(mForceUseFirstFrameRefreshArea
? mFirstFrameRefreshArea
358 : front
->GetDirtyRect());
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
);
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());
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.
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
389 mPending
= newPending
;
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
));
406 return AnimationFrameDiscardingQueue::ResetInternal();
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.
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
;
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;
429 if (mRecycle
.empty()) {
430 return RawAccessFrameRef();
433 RawAccessFrameRef recycledFrame
;
434 if (mRecycle
.front().mFrame
) {
435 recycledFrame
= mRecycle
.front().mFrame
->RawAccessRef();
436 MOZ_ASSERT(recycledFrame
);
437 mRecycle
.pop_front();
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
;
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.
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());
459 for (const RecycleEntry
& entry
: mRecycle
) {
460 aRecycleRect
= aRecycleRect
.Union(entry
.mDirtyRect
);
464 mRecycle
.pop_front();
467 return recycledFrame
;
471 } // namespace mozilla