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"
7 #include "mozilla/Move.h" // for Move
12 AnimationFrameRetainedBuffer::AnimationFrameRetainedBuffer(size_t aThreshold
,
15 : AnimationFrameBuffer(aBatch
, aStartFrame
), mThreshold(aThreshold
) {
16 // To simplify the code, we have the assumption that the threshold for
17 // entering discard-after-display mode is at least twice the batch size (since
18 // that is the most frames-pending-decode we will request) + 1 for the current
19 // frame. That way the redecoded frames being inserted will never risk
20 // overlapping the frames we will discard due to the animation progressing.
21 // That may cause us to use a little more memory than we want but that is an
22 // acceptable tradeoff for simplicity.
23 size_t minThreshold
= 2 * mBatch
+ 1;
24 if (mThreshold
< minThreshold
) {
25 mThreshold
= minThreshold
;
28 // The maximum number of frames we should ever have decoded at one time is
29 // twice the batch. That is a good as number as any to start our decoding at.
30 mPending
= mBatch
* 2;
33 bool AnimationFrameRetainedBuffer::InsertInternal(RefPtr
<imgFrame
>&& aFrame
) {
34 // We should only insert new frames if we actually asked for them.
35 MOZ_ASSERT(!mSizeKnown
);
36 MOZ_ASSERT(mFrames
.Length() < mThreshold
);
39 mFrames
.AppendElement(std::move(aFrame
));
40 MOZ_ASSERT(mSize
== mFrames
.Length());
41 return mSize
< mThreshold
;
44 bool AnimationFrameRetainedBuffer::ResetInternal() {
45 // If we haven't crossed the threshold, then we know by definition we have
46 // not discarded any frames. If we previously requested more frames, but
47 // it would have been more than we would have buffered otherwise, we can
48 // stop the decoding after one more frame.
49 if (mPending
> 1 && mSize
>= mBatch
* 2 + 1) {
50 MOZ_ASSERT(!mSizeKnown
);
54 // Either the decoder is still running, or we have enough frames already.
55 // No need for us to restart it.
59 bool AnimationFrameRetainedBuffer::MarkComplete(
60 const gfx::IntRect
& aFirstFrameRefreshArea
) {
61 MOZ_ASSERT(!mSizeKnown
);
68 void AnimationFrameRetainedBuffer::AdvanceInternal() {
69 // We should not have advanced if we never inserted.
70 MOZ_ASSERT(!mFrames
.IsEmpty());
71 // We only want to change the current frame index if we have advanced. This
72 // means either a higher frame index, or going back to the beginning.
73 size_t framesLength
= mFrames
.Length();
74 // We should never have advanced beyond the frame buffer.
75 MOZ_ASSERT(mGetIndex
< framesLength
);
76 // We should never advance if the current frame is null -- it needs to know
77 // the timeout from it at least to know when to advance.
78 MOZ_ASSERT_IF(mGetIndex
> 0, mFrames
[mGetIndex
- 1]);
79 MOZ_ASSERT_IF(mGetIndex
== 0, mFrames
[framesLength
- 1]);
80 // The owner should have already accessed the next frame, so it should also
82 MOZ_ASSERT(mFrames
[mGetIndex
]);
85 // Calculate how many frames we have requested ahead of the current frame.
86 size_t buffered
= mPending
+ framesLength
- mGetIndex
- 1;
87 if (buffered
< mBatch
) {
88 // If we have fewer frames than the batch size, then ask for more. If we
89 // do not have any pending, then we know that there is no active decoding.
95 imgFrame
* AnimationFrameRetainedBuffer::Get(size_t aFrame
, bool aForDisplay
) {
96 // We should not have asked for a frame if we never inserted.
97 if (mFrames
.IsEmpty()) {
98 MOZ_ASSERT_UNREACHABLE("Calling Get() when we have no frames");
102 // If we don't have that frame, return an empty frame ref.
103 if (aFrame
>= mFrames
.Length()) {
107 // If we have space for the frame, it should always be available.
108 if (!mFrames
[aFrame
]) {
109 MOZ_ASSERT_UNREACHABLE("Calling Get() when frame is unavailable");
113 // If we are advancing on behalf of the animation, we don't expect it to be
114 // getting any frames (besides the first) until we get the desired frame.
115 MOZ_ASSERT(aFrame
== 0 || mAdvance
== 0);
116 return mFrames
[aFrame
].get();
119 bool AnimationFrameRetainedBuffer::IsFirstFrameFinished() const {
120 return !mFrames
.IsEmpty() && mFrames
[0]->IsFinished();
123 bool AnimationFrameRetainedBuffer::IsLastInsertedFrame(imgFrame
* aFrame
) const {
124 return !mFrames
.IsEmpty() && mFrames
.LastElement().get() == aFrame
;
127 void AnimationFrameRetainedBuffer::AddSizeOfExcludingThis(
128 MallocSizeOf aMallocSizeOf
, const AddSizeOfCb
& aCallback
) {
130 for (const RefPtr
<imgFrame
>& frame
: mFrames
) {
132 frame
->AddSizeOfExcludingThis(aMallocSizeOf
,
133 [&](AddSizeOfCbData
& aMetadata
) {
135 aCallback(aMetadata
);
140 AnimationFrameDiscardingQueue::AnimationFrameDiscardingQueue(
141 AnimationFrameRetainedBuffer
&& aQueue
)
142 : AnimationFrameBuffer(aQueue
),
143 mInsertIndex(aQueue
.mFrames
.Length()),
144 mFirstFrame(aQueue
.mFrames
[0]) {
145 MOZ_ASSERT(!mSizeKnown
);
146 MOZ_ASSERT(!mRedecodeError
);
147 MOZ_ASSERT(mInsertIndex
> 0);
150 // We avoided moving aQueue.mFrames[0] for mFirstFrame above because it is
151 // possible the animation was reset back to the beginning, and then we crossed
152 // the threshold without advancing further. That would mean mGetIndex is 0.
153 for (size_t i
= mGetIndex
; i
< mInsertIndex
; ++i
) {
154 MOZ_ASSERT(aQueue
.mFrames
[i
]);
155 mDisplay
.push_back(std::move(aQueue
.mFrames
[i
]));
159 bool AnimationFrameDiscardingQueue::InsertInternal(RefPtr
<imgFrame
>&& aFrame
) {
160 if (mInsertIndex
== mSize
) {
162 // We produced more frames on a subsequent decode than on the first pass.
163 mRedecodeError
= true;
170 // Even though we don't use redecoded first frames for display purposes, we
171 // will still use them for recycling, so we still need to insert it.
172 mDisplay
.push_back(std::move(aFrame
));
174 MOZ_ASSERT(mInsertIndex
<= mSize
);
178 bool AnimationFrameDiscardingQueue::ResetInternal() {
182 bool restartDecoder
= mPending
== 0;
183 mPending
= 2 * mBatch
;
184 return restartDecoder
;
187 bool AnimationFrameDiscardingQueue::MarkComplete(
188 const gfx::IntRect
& aFirstFrameRefreshArea
) {
189 if (NS_WARN_IF(mInsertIndex
!= mSize
)) {
190 mRedecodeError
= true;
194 // We reached the end of the animation, the next frame we get, if we get
195 // another, will be the first frame again.
199 // Since we only request advancing when we want to resume at a certain point
200 // in the animation, we should never exceed the number of frames.
201 MOZ_ASSERT(mAdvance
== 0);
205 void AnimationFrameDiscardingQueue::AdvanceInternal() {
206 // We only want to change the current frame index if we have advanced. This
207 // means either a higher frame index, or going back to the beginning.
208 // We should never have advanced beyond the frame buffer.
209 MOZ_ASSERT(mGetIndex
< mSize
);
211 // We should have the current frame still in the display queue. Either way,
212 // we should at least have an entry in the queue which we need to consume.
213 MOZ_ASSERT(!mDisplay
.empty());
214 MOZ_ASSERT(mDisplay
.front());
215 mDisplay
.pop_front();
216 MOZ_ASSERT(!mDisplay
.empty());
217 MOZ_ASSERT(mDisplay
.front());
219 if (mDisplay
.size() + mPending
- 1 < mBatch
) {
220 // If we have fewer frames than the batch size, then ask for more. If we
221 // do not have any pending, then we know that there is no active decoding.
226 imgFrame
* AnimationFrameDiscardingQueue::Get(size_t aFrame
, bool aForDisplay
) {
227 // The first frame is stored separately. If we only need the frame for
228 // display purposes, we can return it right away. If we need it for advancing
229 // the animation, we want to verify the recreated first frame is available
230 // before allowing it continue.
231 if (aForDisplay
&& aFrame
== 0) {
232 return mFirstFrame
.get();
235 // If we don't have that frame, return an empty frame ref.
236 if (aFrame
>= mSize
) {
241 if (aFrame
>= mGetIndex
) {
242 offset
= aFrame
- mGetIndex
;
243 } else if (!mSizeKnown
) {
244 MOZ_ASSERT_UNREACHABLE("Requesting previous frame after we have advanced!");
247 offset
= mSize
- mGetIndex
+ aFrame
;
250 if (offset
>= mDisplay
.size()) {
254 // If we are advancing on behalf of the animation, we don't expect it to be
255 // getting any frames (besides the first) until we get the desired frame.
256 MOZ_ASSERT(aFrame
== 0 || mAdvance
== 0);
258 // If we have space for the frame, it should always be available.
259 MOZ_ASSERT(mDisplay
[offset
]);
260 return mDisplay
[offset
].get();
263 bool AnimationFrameDiscardingQueue::IsFirstFrameFinished() const {
264 MOZ_ASSERT(mFirstFrame
);
265 MOZ_ASSERT(mFirstFrame
->IsFinished());
269 bool AnimationFrameDiscardingQueue::IsLastInsertedFrame(
270 imgFrame
* aFrame
) const {
271 return !mDisplay
.empty() && mDisplay
.back().get() == aFrame
;
274 void AnimationFrameDiscardingQueue::AddSizeOfExcludingThis(
275 MallocSizeOf aMallocSizeOf
, const AddSizeOfCb
& aCallback
) {
276 mFirstFrame
->AddSizeOfExcludingThis(aMallocSizeOf
,
277 [&](AddSizeOfCbData
& aMetadata
) {
279 aCallback(aMetadata
);
282 size_t i
= mGetIndex
;
283 for (const RefPtr
<imgFrame
>& frame
: mDisplay
) {
287 if (mFirstFrame
.get() == frame
.get()) {
288 // First frame again, we already covered it above. We can have a
289 // different frame in the first frame position in the discard queue
290 // on subsequent passes of the animation. This is useful for recycling.
295 frame
->AddSizeOfExcludingThis(aMallocSizeOf
,
296 [&](AddSizeOfCbData
& aMetadata
) {
298 aCallback(aMetadata
);
303 AnimationFrameRecyclingQueue::AnimationFrameRecyclingQueue(
304 AnimationFrameRetainedBuffer
&& aQueue
)
305 : AnimationFrameDiscardingQueue(std::move(aQueue
)),
306 mForceUseFirstFrameRefreshArea(false) {
307 // In an ideal world, we would always save the already displayed frames for
308 // recycling but none of the frames were marked as recyclable. We will incur
309 // the extra allocation cost for a few more frames.
312 // Until we reach the end of the animation, set the first frame refresh area
313 // to match that of the full area of the first frame.
314 mFirstFrameRefreshArea
= mFirstFrame
->GetRect();
317 void AnimationFrameRecyclingQueue::AddSizeOfExcludingThis(
318 MallocSizeOf aMallocSizeOf
, const AddSizeOfCb
& aCallback
) {
319 AnimationFrameDiscardingQueue::AddSizeOfExcludingThis(aMallocSizeOf
,
322 for (const RecycleEntry
& entry
: mRecycle
) {
324 entry
.mFrame
->AddSizeOfExcludingThis(
325 aMallocSizeOf
, [&](AddSizeOfCbData
& aMetadata
) {
326 aMetadata
.index
= 0; // Frame is not applicable
327 aCallback(aMetadata
);
333 void AnimationFrameRecyclingQueue::AdvanceInternal() {
334 // We only want to change the current frame index if we have advanced. This
335 // means either a higher frame index, or going back to the beginning.
336 // We should never have advanced beyond the frame buffer.
337 MOZ_ASSERT(mGetIndex
< mSize
);
339 MOZ_ASSERT(!mDisplay
.empty());
340 MOZ_ASSERT(mDisplay
.front());
342 // We have advanced past the first frame. That means the next frame we are
343 // putting in the queue to recycling is the first frame in the animation,
344 // and we no longer need to worry about having looped around.
345 if (mGetIndex
== 1) {
346 mForceUseFirstFrameRefreshArea
= false;
349 RefPtr
<imgFrame
>& front
= mDisplay
.front();
350 RecycleEntry
newEntry(mForceUseFirstFrameRefreshArea
? mFirstFrameRefreshArea
351 : front
->GetDirtyRect());
353 // If we are allowed to recycle the frame, then we should save it before the
354 // base class's AdvanceInternal discards it.
355 newEntry
.mFrame
= std::move(front
);
357 // Even if the frame itself isn't saved, we want the dirty rect to calculate
358 // the recycle rect for future recycled frames.
359 mRecycle
.push_back(std::move(newEntry
));
360 mDisplay
.pop_front();
361 MOZ_ASSERT(!mDisplay
.empty());
362 MOZ_ASSERT(mDisplay
.front());
364 if (mDisplay
.size() + mPending
- 1 < mBatch
) {
365 // If we have fewer frames than the batch size, then ask for more. If we
366 // do not have any pending, then we know that there is no active decoding.
368 // We limit the batch to avoid using the frame we just added to the queue.
369 // This gives other parts of the system time to switch to the new current
370 // frame, and maximize buffer reuse. In particular this is useful for
371 // WebRender which holds onto the previous frame for much longer.
372 size_t newPending
= std::min(mPending
+ mBatch
, mRecycle
.size() - 1);
373 if (newPending
== 0 && (mDisplay
.size() <= 1 || mPending
> 0)) {
374 // If we already have pending frames, then the decoder is active and we
375 // cannot go below one. If we are displaying the only frame we have, and
376 // there are none pending, then we must request at least one more frame to
377 // continue to animation, because we won't advance again without a new
378 // frame. This may cause us to skip recycling because the previous frame
382 mPending
= newPending
;
386 bool AnimationFrameRecyclingQueue::ResetInternal() {
387 // We should save any display frames that we can to save on at least the
388 // allocation. The first frame refresh area is guaranteed to be the aggregate
389 // dirty rect or the entire frame, and so the bare minimum area we can
390 // recycle. We don't need to worry about updating the dirty rect for the
391 // existing mRecycle entries, because that will happen in RecycleFrame when
392 // we try to pull out a frame to redecode the first frame.
393 for (RefPtr
<imgFrame
>& frame
: mDisplay
) {
394 RecycleEntry
newEntry(mFirstFrameRefreshArea
);
395 newEntry
.mFrame
= std::move(frame
);
396 mRecycle
.push_back(std::move(newEntry
));
399 return AnimationFrameDiscardingQueue::ResetInternal();
402 RawAccessFrameRef
AnimationFrameRecyclingQueue::RecycleFrame(
403 gfx::IntRect
& aRecycleRect
) {
404 if (mInsertIndex
== 0) {
405 // If we are recreating the first frame, then we actually have already
406 // precomputed aggregate of the dirty rects as the first frame refresh
407 // area. We know that all of the frames still in the recycling queue
408 // need to take into account the same dirty rect because they are also
409 // frames which cross the boundary.
410 for (RecycleEntry
& entry
: mRecycle
) {
411 MOZ_ASSERT(mFirstFrameRefreshArea
.Contains(entry
.mDirtyRect
));
412 entry
.mDirtyRect
= mFirstFrameRefreshArea
;
414 // Until we advance to the first frame again, any subsequent recycled
415 // frames should also use the first frame refresh area.
416 mForceUseFirstFrameRefreshArea
= true;
419 if (mRecycle
.empty()) {
420 return RawAccessFrameRef();
423 RawAccessFrameRef recycledFrame
;
424 if (mRecycle
.front().mFrame
) {
425 recycledFrame
= mRecycle
.front().mFrame
->RawAccessRef();
426 MOZ_ASSERT(recycledFrame
);
427 mRecycle
.pop_front();
429 if (mForceUseFirstFrameRefreshArea
) {
430 // We are still crossing the loop boundary and cannot rely upon the dirty
431 // rects of entries in mDisplay to be representative. E.g. The first frame
432 // is probably has a full frame dirty rect.
433 aRecycleRect
= mFirstFrameRefreshArea
;
435 // Calculate the recycle rect for the recycled frame. This is the
436 // cumulative dirty rect of all of the frames ahead of us to be displayed,
437 // and to be used for recycling. Or in other words, the dirty rect between
438 // the recycled frame and the decoded frame which reuses the buffer.
440 // We know at this point that mRecycle contains either frames from the end
441 // of the animation with the first frame refresh area as the dirty rect
442 // (plus the first frame likewise) and frames with their actual dirty rect
443 // from the start. mDisplay should also only contain frames from the start
444 // of the animation onwards.
445 aRecycleRect
.SetRect(0, 0, 0, 0);
446 for (const RefPtr
<imgFrame
>& frame
: mDisplay
) {
447 aRecycleRect
= aRecycleRect
.Union(frame
->GetDirtyRect());
449 for (const RecycleEntry
& entry
: mRecycle
) {
450 aRecycleRect
= aRecycleRect
.Union(entry
.mDirtyRect
);
454 mRecycle
.pop_front();
457 return recycledFrame
;
460 bool AnimationFrameRecyclingQueue::MarkComplete(
461 const gfx::IntRect
& aFirstFrameRefreshArea
) {
462 bool continueDecoding
=
463 AnimationFrameDiscardingQueue::MarkComplete(aFirstFrameRefreshArea
);
465 // If we encounter a redecode error, just make the first frame refresh area to
466 // be the full frame, because we don't really know what we can safely recycle.
467 mFirstFrameRefreshArea
=
468 mRedecodeError
? mFirstFrame
->GetRect() : aFirstFrameRefreshArea
;
469 return continueDecoding
;
473 } // namespace mozilla