Bug 1687064 [wpt PR 27207] - Update wpt metadata, a=testonly
[gecko.git] / image / AnimationFrameBuffer.cpp
blobbcaacfce5f3be31aacac899d9c07298233593073
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
10 namespace mozilla {
11 namespace image {
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;
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);
39 ++mSize;
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);
52 mPending = 1;
55 // Either the decoder is still running, or we have enough frames already.
56 // No need for us to restart it.
57 return false;
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;
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]);
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;
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;
103 // If we don't have that frame, return an empty frame ref.
104 if (aFrame >= mFrames.Length()) {
105 return nullptr;
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;
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();
120 bool AnimationFrameRetainedBuffer::IsFirstFrameFinished() const {
121 return !mFrames.IsEmpty() && mFrames[0]->IsFinished();
124 bool AnimationFrameRetainedBuffer::IsLastInsertedFrame(imgFrame* aFrame) const {
125 return !mFrames.IsEmpty() && mFrames.LastElement().get() == aFrame;
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);
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;
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]));
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;
168 ++mSize;
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;
179 bool AnimationFrameDiscardingQueue::ResetInternal() {
180 mDisplay.clear();
181 mInsertIndex = 0;
183 bool restartDecoder = mPending == 0;
184 mPending = 2 * mBatch;
185 return restartDecoder;
188 bool AnimationFrameDiscardingQueue::MarkComplete(
189 const gfx::IntRect& aFirstFrameRefreshArea) {
190 if (NS_WARN_IF(mInsertIndex != mSize)) {
191 mRedecodeError = true;
192 mPending = 0;
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;
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;
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);
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());
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;
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();
236 // If we don't have that frame, return an empty frame ref.
237 if (aFrame >= mSize) {
238 return nullptr;
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;
251 if (offset >= mDisplay.size()) {
252 return nullptr;
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);
259 // If we have space for the frame, it should always be available.
260 MOZ_ASSERT(mDisplay[offset]);
261 return mDisplay[offset].get();
264 bool AnimationFrameDiscardingQueue::IsFirstFrameFinished() const {
265 MOZ_ASSERT(mFirstFrame);
266 MOZ_ASSERT(mFirstFrame->IsFinished());
267 return true;
270 bool AnimationFrameDiscardingQueue::IsLastInsertedFrame(
271 imgFrame* aFrame) const {
272 return !mDisplay.empty() && mDisplay.back().get() == aFrame;
275 void AnimationFrameDiscardingQueue::AddSizeOfExcludingThis(
276 MallocSizeOf aMallocSizeOf, const AddSizeOfCb& aCallback) {
277 mFirstFrame->AddSizeOfExcludingThis(aMallocSizeOf,
278 [&](AddSizeOfCbData& aMetadata) {
279 aMetadata.mIndex = 1;
280 aCallback(aMetadata);
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;
296 frame->AddSizeOfExcludingThis(aMallocSizeOf,
297 [&](AddSizeOfCbData& aMetadata) {
298 aMetadata.mIndex = i;
299 aCallback(aMetadata);
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;
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();
318 void AnimationFrameRecyclingQueue::AddSizeOfExcludingThis(
319 MallocSizeOf aMallocSizeOf, const AddSizeOfCb& aCallback) {
320 AnimationFrameDiscardingQueue::AddSizeOfExcludingThis(aMallocSizeOf,
321 aCallback);
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);
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);
340 MOZ_ASSERT(!mDisplay.empty());
341 MOZ_ASSERT(mDisplay.front());
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;
350 RefPtr<imgFrame>& front = mDisplay.front();
351 RecycleEntry newEntry(mForceUseFirstFrameRefreshArea ? mFirstFrameRefreshArea
352 : front->GetDirtyRect());
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);
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());
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.
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;
383 mPending = newPending;
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));
400 return AnimationFrameDiscardingQueue::ResetInternal();
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;
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;
420 if (mRecycle.empty()) {
421 return RawAccessFrameRef();
424 RawAccessFrameRef recycledFrame;
425 if (mRecycle.front().mFrame) {
426 recycledFrame = mRecycle.front().mFrame->RawAccessRef();
427 MOZ_ASSERT(recycledFrame);
428 mRecycle.pop_front();
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.
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());
450 for (const RecycleEntry& entry : mRecycle) {
451 aRecycleRect = aRecycleRect.Union(entry.mDirtyRect);
454 } else {
455 mRecycle.pop_front();
458 return recycledFrame;
461 bool AnimationFrameRecyclingQueue::MarkComplete(
462 const gfx::IntRect& aFirstFrameRefreshArea) {
463 bool continueDecoding =
464 AnimationFrameDiscardingQueue::MarkComplete(aFirstFrameRefreshArea);
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;
473 } // namespace image
474 } // namespace mozilla