Bug 1543288 - Update the Print icon and Add rules button position. r=mtigley
[gecko.git] / image / AnimationFrameBuffer.cpp
blob23065295733ba95e151f1c8cba9d272daa8ba555
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
9 namespace mozilla {
10 namespace image {
12 AnimationFrameRetainedBuffer::AnimationFrameRetainedBuffer(size_t aThreshold,
13 size_t aBatch,
14 size_t aStartFrame)
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);
38 ++mSize;
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);
51 mPending = 1;
54 // Either the decoder is still running, or we have enough frames already.
55 // No need for us to restart it.
56 return false;
59 bool AnimationFrameRetainedBuffer::MarkComplete(
60 const gfx::IntRect& aFirstFrameRefreshArea) {
61 MOZ_ASSERT(!mSizeKnown);
62 mSizeKnown = true;
63 mPending = 0;
64 mFrames.Compact();
65 return false;
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
81 // be available.
82 MOZ_ASSERT(mFrames[mGetIndex]);
84 if (!mSizeKnown) {
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.
90 mPending += mBatch;
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");
99 return nullptr;
102 // If we don't have that frame, return an empty frame ref.
103 if (aFrame >= mFrames.Length()) {
104 return nullptr;
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");
110 return nullptr;
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) {
129 size_t i = 0;
130 for (const RefPtr<imgFrame>& frame : mFrames) {
131 ++i;
132 frame->AddSizeOfExcludingThis(aMallocSizeOf,
133 [&](AddSizeOfCbData& aMetadata) {
134 aMetadata.index = i;
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);
148 mMayDiscard = true;
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) {
161 if (mSizeKnown) {
162 // We produced more frames on a subsequent decode than on the first pass.
163 mRedecodeError = true;
164 mPending = 0;
165 return true;
167 ++mSize;
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));
173 ++mInsertIndex;
174 MOZ_ASSERT(mInsertIndex <= mSize);
175 return true;
178 bool AnimationFrameDiscardingQueue::ResetInternal() {
179 mDisplay.clear();
180 mInsertIndex = 0;
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;
191 mPending = 0;
194 // We reached the end of the animation, the next frame we get, if we get
195 // another, will be the first frame again.
196 mInsertIndex = 0;
197 mSizeKnown = true;
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);
202 return mPending > 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.
222 mPending += mBatch;
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) {
237 return nullptr;
240 size_t offset;
241 if (aFrame >= mGetIndex) {
242 offset = aFrame - mGetIndex;
243 } else if (!mSizeKnown) {
244 MOZ_ASSERT_UNREACHABLE("Requesting previous frame after we have advanced!");
245 return nullptr;
246 } else {
247 offset = mSize - mGetIndex + aFrame;
250 if (offset >= mDisplay.size()) {
251 return nullptr;
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());
266 return true;
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) {
278 aMetadata.index = 1;
279 aCallback(aMetadata);
282 size_t i = mGetIndex;
283 for (const RefPtr<imgFrame>& frame : mDisplay) {
284 ++i;
285 if (mSize < i) {
286 i = 1;
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.
291 continue;
295 frame->AddSizeOfExcludingThis(aMallocSizeOf,
296 [&](AddSizeOfCbData& aMetadata) {
297 aMetadata.index = i;
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.
310 mRecycling = true;
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,
320 aCallback);
322 for (const RecycleEntry& entry : mRecycle) {
323 if (entry.mFrame) {
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
379 // is still in use.
380 newPending = 1;
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;
434 } else {
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);
453 } else {
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;
472 } // namespace image
473 } // namespace mozilla