mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			471 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			471 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
#include "AnimationFrameBuffer.h"
 | 
						|
 | 
						|
#include <utility>  // for Move
 | 
						|
 | 
						|
namespace mozilla {
 | 
						|
namespace image {
 | 
						|
 | 
						|
AnimationFrameRetainedBuffer::AnimationFrameRetainedBuffer(size_t aThreshold,
 | 
						|
                                                           size_t aBatch,
 | 
						|
                                                           size_t aStartFrame)
 | 
						|
    : AnimationFrameBuffer(aBatch, aStartFrame), mThreshold(aThreshold) {
 | 
						|
  // To simplify the code, we have the assumption that the threshold for
 | 
						|
  // entering discard-after-display mode is at least twice the batch size (since
 | 
						|
  // that is the most frames-pending-decode we will request) + 1 for the current
 | 
						|
  // frame. That way the redecoded frames being inserted will never risk
 | 
						|
  // overlapping the frames we will discard due to the animation progressing.
 | 
						|
  // That may cause us to use a little more memory than we want but that is an
 | 
						|
  // acceptable tradeoff for simplicity.
 | 
						|
  size_t minThreshold = 2 * mBatch + 1;
 | 
						|
  if (mThreshold < minThreshold) {
 | 
						|
    mThreshold = minThreshold;
 | 
						|
  }
 | 
						|
 | 
						|
  // The maximum number of frames we should ever have decoded at one time is
 | 
						|
  // twice the batch. That is a good as number as any to start our decoding at.
 | 
						|
  mPending = mBatch * 2;
 | 
						|
}
 | 
						|
 | 
						|
bool AnimationFrameRetainedBuffer::InsertInternal(RefPtr<imgFrame>&& aFrame) {
 | 
						|
  // We should only insert new frames if we actually asked for them.
 | 
						|
  MOZ_ASSERT(!mSizeKnown);
 | 
						|
  MOZ_ASSERT(mFrames.Length() < mThreshold);
 | 
						|
 | 
						|
  ++mSize;
 | 
						|
  mFrames.AppendElement(std::move(aFrame));
 | 
						|
  MOZ_ASSERT(mSize == mFrames.Length());
 | 
						|
  return mSize < mThreshold;
 | 
						|
}
 | 
						|
 | 
						|
bool AnimationFrameRetainedBuffer::ResetInternal() {
 | 
						|
  // If we haven't crossed the threshold, then we know by definition we have
 | 
						|
  // not discarded any frames. If we previously requested more frames, but
 | 
						|
  // it would have been more than we would have buffered otherwise, we can
 | 
						|
  // stop the decoding after one more frame.
 | 
						|
  if (mPending > 1 && mSize >= mBatch * 2 + 1) {
 | 
						|
    MOZ_ASSERT(!mSizeKnown);
 | 
						|
    mPending = 1;
 | 
						|
  }
 | 
						|
 | 
						|
  // Either the decoder is still running, or we have enough frames already.
 | 
						|
  // No need for us to restart it.
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
bool AnimationFrameRetainedBuffer::MarkComplete(
 | 
						|
    const gfx::IntRect& aFirstFrameRefreshArea) {
 | 
						|
  MOZ_ASSERT(!mSizeKnown);
 | 
						|
  mFirstFrameRefreshArea = aFirstFrameRefreshArea;
 | 
						|
  mSizeKnown = true;
 | 
						|
  mPending = 0;
 | 
						|
  mFrames.Compact();
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
void AnimationFrameRetainedBuffer::AdvanceInternal() {
 | 
						|
  // We should not have advanced if we never inserted.
 | 
						|
  MOZ_ASSERT(!mFrames.IsEmpty());
 | 
						|
  // We only want to change the current frame index if we have advanced. This
 | 
						|
  // means either a higher frame index, or going back to the beginning.
 | 
						|
  size_t framesLength = mFrames.Length();
 | 
						|
  // We should never have advanced beyond the frame buffer.
 | 
						|
  MOZ_ASSERT(mGetIndex < framesLength);
 | 
						|
  // We should never advance if the current frame is null -- it needs to know
 | 
						|
  // the timeout from it at least to know when to advance.
 | 
						|
  MOZ_ASSERT_IF(mGetIndex > 0, mFrames[mGetIndex - 1]);
 | 
						|
  MOZ_ASSERT_IF(mGetIndex == 0, mFrames[framesLength - 1]);
 | 
						|
  // The owner should have already accessed the next frame, so it should also
 | 
						|
  // be available.
 | 
						|
  MOZ_ASSERT(mFrames[mGetIndex]);
 | 
						|
 | 
						|
  if (!mSizeKnown) {
 | 
						|
    // Calculate how many frames we have requested ahead of the current frame.
 | 
						|
    size_t buffered = mPending + framesLength - mGetIndex - 1;
 | 
						|
    if (buffered < mBatch) {
 | 
						|
      // If we have fewer frames than the batch size, then ask for more. If we
 | 
						|
      // do not have any pending, then we know that there is no active decoding.
 | 
						|
      mPending += mBatch;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
imgFrame* AnimationFrameRetainedBuffer::Get(size_t aFrame, bool aForDisplay) {
 | 
						|
  // We should not have asked for a frame if we never inserted.
 | 
						|
  if (mFrames.IsEmpty()) {
 | 
						|
    MOZ_ASSERT_UNREACHABLE("Calling Get() when we have no frames");
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  // If we don't have that frame, return an empty frame ref.
 | 
						|
  if (aFrame >= mFrames.Length()) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  // If we have space for the frame, it should always be available.
 | 
						|
  if (!mFrames[aFrame]) {
 | 
						|
    MOZ_ASSERT_UNREACHABLE("Calling Get() when frame is unavailable");
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  // If we are advancing on behalf of the animation, we don't expect it to be
 | 
						|
  // getting any frames (besides the first) until we get the desired frame.
 | 
						|
  MOZ_ASSERT(aFrame == 0 || mAdvance == 0);
 | 
						|
  return mFrames[aFrame].get();
 | 
						|
}
 | 
						|
 | 
						|
bool AnimationFrameRetainedBuffer::IsFirstFrameFinished() const {
 | 
						|
  return !mFrames.IsEmpty() && mFrames[0]->IsFinished();
 | 
						|
}
 | 
						|
 | 
						|
bool AnimationFrameRetainedBuffer::IsLastInsertedFrame(imgFrame* aFrame) const {
 | 
						|
  return !mFrames.IsEmpty() && mFrames.LastElement().get() == aFrame;
 | 
						|
}
 | 
						|
 | 
						|
void AnimationFrameRetainedBuffer::AddSizeOfExcludingThis(
 | 
						|
    MallocSizeOf aMallocSizeOf, const AddSizeOfCb& aCallback) {
 | 
						|
  size_t i = 0;
 | 
						|
  for (const RefPtr<imgFrame>& frame : mFrames) {
 | 
						|
    ++i;
 | 
						|
    frame->AddSizeOfExcludingThis(aMallocSizeOf,
 | 
						|
                                  [&](AddSizeOfCbData& aMetadata) {
 | 
						|
                                    aMetadata.mIndex = i;
 | 
						|
                                    aCallback(aMetadata);
 | 
						|
                                  });
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
AnimationFrameDiscardingQueue::AnimationFrameDiscardingQueue(
 | 
						|
    AnimationFrameRetainedBuffer&& aQueue)
 | 
						|
    : AnimationFrameBuffer(aQueue),
 | 
						|
      mInsertIndex(aQueue.mFrames.Length()),
 | 
						|
      mFirstFrame(aQueue.mFrames[0]) {
 | 
						|
  MOZ_ASSERT(!mSizeKnown);
 | 
						|
  MOZ_ASSERT(!mRedecodeError);
 | 
						|
  MOZ_ASSERT(mInsertIndex > 0);
 | 
						|
  mMayDiscard = true;
 | 
						|
 | 
						|
  // We avoided moving aQueue.mFrames[0] for mFirstFrame above because it is
 | 
						|
  // possible the animation was reset back to the beginning, and then we crossed
 | 
						|
  // the threshold without advancing further. That would mean mGetIndex is 0.
 | 
						|
  for (size_t i = mGetIndex; i < mInsertIndex; ++i) {
 | 
						|
    MOZ_ASSERT(aQueue.mFrames[i]);
 | 
						|
    mDisplay.push_back(std::move(aQueue.mFrames[i]));
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool AnimationFrameDiscardingQueue::InsertInternal(RefPtr<imgFrame>&& aFrame) {
 | 
						|
  if (mInsertIndex == mSize) {
 | 
						|
    if (mSizeKnown) {
 | 
						|
      // We produced more frames on a subsequent decode than on the first pass.
 | 
						|
      mRedecodeError = true;
 | 
						|
      mPending = 0;
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    ++mSize;
 | 
						|
  }
 | 
						|
 | 
						|
  // Even though we don't use redecoded first frames for display purposes, we
 | 
						|
  // will still use them for recycling, so we still need to insert it.
 | 
						|
  mDisplay.push_back(std::move(aFrame));
 | 
						|
  ++mInsertIndex;
 | 
						|
  MOZ_ASSERT(mInsertIndex <= mSize);
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool AnimationFrameDiscardingQueue::ResetInternal() {
 | 
						|
  mDisplay.clear();
 | 
						|
  mInsertIndex = 0;
 | 
						|
 | 
						|
  bool restartDecoder = mPending == 0;
 | 
						|
  mPending = 2 * mBatch;
 | 
						|
  return restartDecoder;
 | 
						|
}
 | 
						|
 | 
						|
bool AnimationFrameDiscardingQueue::MarkComplete(
 | 
						|
    const gfx::IntRect& aFirstFrameRefreshArea) {
 | 
						|
  if (NS_WARN_IF(mInsertIndex != mSize)) {
 | 
						|
    mRedecodeError = true;
 | 
						|
    mPending = 0;
 | 
						|
  }
 | 
						|
 | 
						|
  // If we encounter a redecode error, just make the first frame refresh area to
 | 
						|
  // be the full frame, because we don't really know what we can safely recycle.
 | 
						|
  mFirstFrameRefreshArea =
 | 
						|
      mRedecodeError ? mFirstFrame->GetRect() : aFirstFrameRefreshArea;
 | 
						|
 | 
						|
  // We reached the end of the animation, the next frame we get, if we get
 | 
						|
  // another, will be the first frame again.
 | 
						|
  mInsertIndex = 0;
 | 
						|
  mSizeKnown = true;
 | 
						|
 | 
						|
  // Since we only request advancing when we want to resume at a certain point
 | 
						|
  // in the animation, we should never exceed the number of frames.
 | 
						|
  MOZ_ASSERT(mAdvance == 0);
 | 
						|
  return mPending > 0;
 | 
						|
}
 | 
						|
 | 
						|
void AnimationFrameDiscardingQueue::AdvanceInternal() {
 | 
						|
  // We only want to change the current frame index if we have advanced. This
 | 
						|
  // means either a higher frame index, or going back to the beginning.
 | 
						|
  // We should never have advanced beyond the frame buffer.
 | 
						|
  MOZ_ASSERT(mGetIndex < mSize);
 | 
						|
 | 
						|
  // We should have the current frame still in the display queue. Either way,
 | 
						|
  // we should at least have an entry in the queue which we need to consume.
 | 
						|
  MOZ_ASSERT(!mDisplay.empty());
 | 
						|
  MOZ_ASSERT(mDisplay.front());
 | 
						|
  mDisplay.pop_front();
 | 
						|
  MOZ_ASSERT(!mDisplay.empty());
 | 
						|
  MOZ_ASSERT(mDisplay.front());
 | 
						|
 | 
						|
  if (mDisplay.size() + mPending - 1 < mBatch) {
 | 
						|
    // If we have fewer frames than the batch size, then ask for more. If we
 | 
						|
    // do not have any pending, then we know that there is no active decoding.
 | 
						|
    mPending += mBatch;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
imgFrame* AnimationFrameDiscardingQueue::Get(size_t aFrame, bool aForDisplay) {
 | 
						|
  // The first frame is stored separately. If we only need the frame for
 | 
						|
  // display purposes, we can return it right away. If we need it for advancing
 | 
						|
  // the animation, we want to verify the recreated first frame is available
 | 
						|
  // before allowing it continue.
 | 
						|
  if (aForDisplay && aFrame == 0) {
 | 
						|
    return mFirstFrame.get();
 | 
						|
  }
 | 
						|
 | 
						|
  // If we don't have that frame, return an empty frame ref.
 | 
						|
  if (aFrame >= mSize) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  size_t offset;
 | 
						|
  if (aFrame >= mGetIndex) {
 | 
						|
    offset = aFrame - mGetIndex;
 | 
						|
  } else if (!mSizeKnown) {
 | 
						|
    MOZ_ASSERT_UNREACHABLE("Requesting previous frame after we have advanced!");
 | 
						|
    return nullptr;
 | 
						|
  } else {
 | 
						|
    offset = mSize - mGetIndex + aFrame;
 | 
						|
  }
 | 
						|
 | 
						|
  if (offset >= mDisplay.size()) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  // If we are advancing on behalf of the animation, we don't expect it to be
 | 
						|
  // getting any frames (besides the first) until we get the desired frame.
 | 
						|
  MOZ_ASSERT(aFrame == 0 || mAdvance == 0);
 | 
						|
 | 
						|
  // If we have space for the frame, it should always be available.
 | 
						|
  MOZ_ASSERT(mDisplay[offset]);
 | 
						|
  return mDisplay[offset].get();
 | 
						|
}
 | 
						|
 | 
						|
bool AnimationFrameDiscardingQueue::IsFirstFrameFinished() const {
 | 
						|
  MOZ_ASSERT(mFirstFrame);
 | 
						|
  MOZ_ASSERT(mFirstFrame->IsFinished());
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool AnimationFrameDiscardingQueue::IsLastInsertedFrame(
 | 
						|
    imgFrame* aFrame) const {
 | 
						|
  return !mDisplay.empty() && mDisplay.back().get() == aFrame;
 | 
						|
}
 | 
						|
 | 
						|
void AnimationFrameDiscardingQueue::AddSizeOfExcludingThis(
 | 
						|
    MallocSizeOf aMallocSizeOf, const AddSizeOfCb& aCallback) {
 | 
						|
  mFirstFrame->AddSizeOfExcludingThis(aMallocSizeOf,
 | 
						|
                                      [&](AddSizeOfCbData& aMetadata) {
 | 
						|
                                        aMetadata.mIndex = 1;
 | 
						|
                                        aCallback(aMetadata);
 | 
						|
                                      });
 | 
						|
 | 
						|
  size_t i = mGetIndex;
 | 
						|
  for (const RefPtr<imgFrame>& frame : mDisplay) {
 | 
						|
    ++i;
 | 
						|
    if (mSize < i) {
 | 
						|
      i = 1;
 | 
						|
      if (mFirstFrame.get() == frame.get()) {
 | 
						|
        // First frame again, we already covered it above. We can have a
 | 
						|
        // different frame in the first frame position in the discard queue
 | 
						|
        // on subsequent passes of the animation. This is useful for recycling.
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    frame->AddSizeOfExcludingThis(aMallocSizeOf,
 | 
						|
                                  [&](AddSizeOfCbData& aMetadata) {
 | 
						|
                                    aMetadata.mIndex = i;
 | 
						|
                                    aCallback(aMetadata);
 | 
						|
                                  });
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
AnimationFrameRecyclingQueue::AnimationFrameRecyclingQueue(
 | 
						|
    AnimationFrameRetainedBuffer&& aQueue)
 | 
						|
    : AnimationFrameDiscardingQueue(std::move(aQueue)),
 | 
						|
      mForceUseFirstFrameRefreshArea(false) {
 | 
						|
  // In an ideal world, we would always save the already displayed frames for
 | 
						|
  // recycling but none of the frames were marked as recyclable. We will incur
 | 
						|
  // the extra allocation cost for a few more frames.
 | 
						|
  mRecycling = true;
 | 
						|
 | 
						|
  // Until we reach the end of the animation, set the first frame refresh area
 | 
						|
  // to match that of the full area of the first frame.
 | 
						|
  mFirstFrameRefreshArea = mFirstFrame->GetRect();
 | 
						|
}
 | 
						|
 | 
						|
void AnimationFrameRecyclingQueue::AddSizeOfExcludingThis(
 | 
						|
    MallocSizeOf aMallocSizeOf, const AddSizeOfCb& aCallback) {
 | 
						|
  AnimationFrameDiscardingQueue::AddSizeOfExcludingThis(aMallocSizeOf,
 | 
						|
                                                        aCallback);
 | 
						|
 | 
						|
  for (const RecycleEntry& entry : mRecycle) {
 | 
						|
    if (entry.mFrame) {
 | 
						|
      entry.mFrame->AddSizeOfExcludingThis(
 | 
						|
          aMallocSizeOf, [&](AddSizeOfCbData& aMetadata) {
 | 
						|
            aMetadata.mIndex = 0;  // Frame is not applicable
 | 
						|
            aCallback(aMetadata);
 | 
						|
          });
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void AnimationFrameRecyclingQueue::AdvanceInternal() {
 | 
						|
  // We only want to change the current frame index if we have advanced. This
 | 
						|
  // means either a higher frame index, or going back to the beginning.
 | 
						|
  // We should never have advanced beyond the frame buffer.
 | 
						|
  MOZ_ASSERT(mGetIndex < mSize);
 | 
						|
 | 
						|
  MOZ_ASSERT(!mDisplay.empty());
 | 
						|
  MOZ_ASSERT(mDisplay.front());
 | 
						|
 | 
						|
  // We have advanced past the first frame. That means the next frame we are
 | 
						|
  // putting in the queue to recycling is the first frame in the animation,
 | 
						|
  // and we no longer need to worry about having looped around.
 | 
						|
  if (mGetIndex == 1) {
 | 
						|
    mForceUseFirstFrameRefreshArea = false;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<imgFrame>& front = mDisplay.front();
 | 
						|
  RecycleEntry newEntry(mForceUseFirstFrameRefreshArea ? mFirstFrameRefreshArea
 | 
						|
                                                       : front->GetDirtyRect());
 | 
						|
 | 
						|
  // If we are allowed to recycle the frame, then we should save it before the
 | 
						|
  // base class's AdvanceInternal discards it.
 | 
						|
  newEntry.mFrame = std::move(front);
 | 
						|
 | 
						|
  // Even if the frame itself isn't saved, we want the dirty rect to calculate
 | 
						|
  // the recycle rect for future recycled frames.
 | 
						|
  mRecycle.push_back(std::move(newEntry));
 | 
						|
  mDisplay.pop_front();
 | 
						|
  MOZ_ASSERT(!mDisplay.empty());
 | 
						|
  MOZ_ASSERT(mDisplay.front());
 | 
						|
 | 
						|
  if (mDisplay.size() + mPending - 1 < mBatch) {
 | 
						|
    // If we have fewer frames than the batch size, then ask for more. If we
 | 
						|
    // do not have any pending, then we know that there is no active decoding.
 | 
						|
    //
 | 
						|
    // We limit the batch to avoid using the frame we just added to the queue.
 | 
						|
    // This gives other parts of the system time to switch to the new current
 | 
						|
    // frame, and maximize buffer reuse. In particular this is useful for
 | 
						|
    // WebRender which holds onto the previous frame for much longer.
 | 
						|
    size_t newPending = std::min(mPending + mBatch, mRecycle.size() - 1);
 | 
						|
    if (newPending == 0 && (mDisplay.size() <= 1 || mPending > 0)) {
 | 
						|
      // If we already have pending frames, then the decoder is active and we
 | 
						|
      // cannot go below one. If we are displaying the only frame we have, and
 | 
						|
      // there are none pending, then we must request at least one more frame to
 | 
						|
      // continue to animation, because we won't advance again without a new
 | 
						|
      // frame. This may cause us to skip recycling because the previous frame
 | 
						|
      // is still in use.
 | 
						|
      newPending = 1;
 | 
						|
    }
 | 
						|
    mPending = newPending;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool AnimationFrameRecyclingQueue::ResetInternal() {
 | 
						|
  // We should save any display frames that we can to save on at least the
 | 
						|
  // allocation. The first frame refresh area is guaranteed to be the aggregate
 | 
						|
  // dirty rect or the entire frame, and so the bare minimum area we can
 | 
						|
  // recycle. We don't need to worry about updating the dirty rect for the
 | 
						|
  // existing mRecycle entries, because that will happen in RecycleFrame when
 | 
						|
  // we try to pull out a frame to redecode the first frame.
 | 
						|
  for (RefPtr<imgFrame>& frame : mDisplay) {
 | 
						|
    RecycleEntry newEntry(mFirstFrameRefreshArea);
 | 
						|
    newEntry.mFrame = std::move(frame);
 | 
						|
    mRecycle.push_back(std::move(newEntry));
 | 
						|
  }
 | 
						|
 | 
						|
  return AnimationFrameDiscardingQueue::ResetInternal();
 | 
						|
}
 | 
						|
 | 
						|
RawAccessFrameRef AnimationFrameRecyclingQueue::RecycleFrame(
 | 
						|
    gfx::IntRect& aRecycleRect) {
 | 
						|
  if (mInsertIndex == 0) {
 | 
						|
    // If we are recreating the first frame, then we actually have already
 | 
						|
    // precomputed aggregate of the dirty rects as the first frame refresh
 | 
						|
    // area. We know that all of the frames still in the recycling queue
 | 
						|
    // need to take into account the same dirty rect because they are also
 | 
						|
    // frames which cross the boundary.
 | 
						|
    //
 | 
						|
    // Note that this may actually shrink the dirty rect if we estimated it
 | 
						|
    // earlier with the full frame size and now we have the actual, more
 | 
						|
    // conservative aggregate for the animation.
 | 
						|
    for (RecycleEntry& entry : mRecycle) {
 | 
						|
      entry.mDirtyRect = mFirstFrameRefreshArea;
 | 
						|
    }
 | 
						|
    // Until we advance to the first frame again, any subsequent recycled
 | 
						|
    // frames should also use the first frame refresh area.
 | 
						|
    mForceUseFirstFrameRefreshArea = true;
 | 
						|
  }
 | 
						|
 | 
						|
  if (mRecycle.empty()) {
 | 
						|
    return RawAccessFrameRef();
 | 
						|
  }
 | 
						|
 | 
						|
  RawAccessFrameRef recycledFrame;
 | 
						|
  if (mRecycle.front().mFrame) {
 | 
						|
    recycledFrame = mRecycle.front().mFrame->RawAccessRef();
 | 
						|
    MOZ_ASSERT(recycledFrame);
 | 
						|
    mRecycle.pop_front();
 | 
						|
 | 
						|
    if (mForceUseFirstFrameRefreshArea) {
 | 
						|
      // We are still crossing the loop boundary and cannot rely upon the dirty
 | 
						|
      // rects of entries in mDisplay to be representative. E.g. The first frame
 | 
						|
      // is probably has a full frame dirty rect.
 | 
						|
      aRecycleRect = mFirstFrameRefreshArea;
 | 
						|
    } else {
 | 
						|
      // Calculate the recycle rect for the recycled frame. This is the
 | 
						|
      // cumulative dirty rect of all of the frames ahead of us to be displayed,
 | 
						|
      // and to be used for recycling. Or in other words, the dirty rect between
 | 
						|
      // the recycled frame and the decoded frame which reuses the buffer.
 | 
						|
      //
 | 
						|
      // We know at this point that mRecycle contains either frames from the end
 | 
						|
      // of the animation with the first frame refresh area as the dirty rect
 | 
						|
      // (plus the first frame likewise) and frames with their actual dirty rect
 | 
						|
      // from the start. mDisplay should also only contain frames from the start
 | 
						|
      // of the animation onwards.
 | 
						|
      aRecycleRect.SetRect(0, 0, 0, 0);
 | 
						|
      for (const RefPtr<imgFrame>& frame : mDisplay) {
 | 
						|
        aRecycleRect = aRecycleRect.Union(frame->GetDirtyRect());
 | 
						|
      }
 | 
						|
      for (const RecycleEntry& entry : mRecycle) {
 | 
						|
        aRecycleRect = aRecycleRect.Union(entry.mDirtyRect);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    mRecycle.pop_front();
 | 
						|
  }
 | 
						|
 | 
						|
  return recycledFrame;
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace image
 | 
						|
}  // namespace mozilla
 |