forked from mirrors/gecko-dev
		
	 fffb25b74f
			
		
	
	
		fffb25b74f
		
	
	
	
	
		
			
			This was done automatically replacing: s/mozilla::Move/std::move/ s/ Move(/ std::move(/ s/(Move(/(std::move(/ Removing the 'using mozilla::Move;' lines. And then with a few manual fixups, see the bug for the split series.. MozReview-Commit-ID: Jxze3adipUh
		
			
				
	
	
		
			324 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			324 lines
		
	
	
	
		
			11 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 "mozilla/Move.h"             // for Move
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace image {
 | |
| 
 | |
| AnimationFrameBuffer::AnimationFrameBuffer()
 | |
|   : mThreshold(0)
 | |
|   , mBatch(0)
 | |
|   , mPending(0)
 | |
|   , mAdvance(0)
 | |
|   , mInsertIndex(0)
 | |
|   , mGetIndex(0)
 | |
|   , mSizeKnown(false)
 | |
|   , mRedecodeError(false)
 | |
| { }
 | |
| 
 | |
| void
 | |
| AnimationFrameBuffer::Initialize(size_t aThreshold,
 | |
|                                  size_t aBatch,
 | |
|                                  size_t aStartFrame)
 | |
| {
 | |
|   MOZ_ASSERT(mThreshold == 0);
 | |
|   MOZ_ASSERT(mBatch == 0);
 | |
|   MOZ_ASSERT(mPending == 0);
 | |
|   MOZ_ASSERT(mAdvance == 0);
 | |
|   MOZ_ASSERT(mFrames.IsEmpty());
 | |
| 
 | |
|   mThreshold = aThreshold;
 | |
|   mBatch = aBatch;
 | |
|   mAdvance = aStartFrame;
 | |
| 
 | |
|   if (mBatch > SIZE_MAX/4) {
 | |
|     // Batch size is so big, we will just end up decoding the whole animation.
 | |
|     mBatch = SIZE_MAX/4;
 | |
|   } else if (mBatch < 1) {
 | |
|     // Never permit a batch size smaller than 1. We always want to be asking for
 | |
|     // at least one frame to start.
 | |
|     mBatch = 1;
 | |
|   }
 | |
| 
 | |
|   // 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
 | |
| AnimationFrameBuffer::Insert(RawAccessFrameRef&& aFrame)
 | |
| {
 | |
|   // We should only insert new frames if we actually asked for them.
 | |
|   MOZ_ASSERT(mPending > 0);
 | |
| 
 | |
|   if (mSizeKnown) {
 | |
|     // We only insert after the size is known if we are repeating the animation
 | |
|     // and we did not keep all of the frames. Replace whatever is there
 | |
|     // (probably an empty frame) with the new frame.
 | |
|     MOZ_ASSERT(MayDiscard());
 | |
| 
 | |
|     // The first decode produced fewer frames than the redecodes, presumably
 | |
|     // because it hit an out-of-memory error which later attempts avoided. Just
 | |
|     // stop the animation because we can't tell the image that we have more
 | |
|     // frames now.
 | |
|     if (mInsertIndex >= mFrames.Length()) {
 | |
|       mRedecodeError = true;
 | |
|       mPending = 0;
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     if (mInsertIndex > 0) {
 | |
|       MOZ_ASSERT(!mFrames[mInsertIndex]);
 | |
|       mFrames[mInsertIndex] = std::move(aFrame);
 | |
|     }
 | |
|   } else if (mInsertIndex == mFrames.Length()) {
 | |
|     // We are still on the first pass of the animation decoding, so this is
 | |
|     // the first time we have seen this frame.
 | |
|     mFrames.AppendElement(std::move(aFrame));
 | |
| 
 | |
|     if (mInsertIndex == mThreshold) {
 | |
|       // We just tripped over the threshold for the first time. This is our
 | |
|       // chance to do any clearing of already displayed frames. After this,
 | |
|       // we only need to release as we advance or force a restart.
 | |
|       MOZ_ASSERT(MayDiscard());
 | |
|       MOZ_ASSERT(mGetIndex < mInsertIndex);
 | |
|       for (size_t i = 1; i < mGetIndex; ++i) {
 | |
|         RawAccessFrameRef discard = std::move(mFrames[i]);
 | |
|       }
 | |
|     }
 | |
|   } else if (mInsertIndex > 0) {
 | |
|     // We were forced to restart an animation before we decoded the last
 | |
|     // frame. If we were discarding frames, then we tossed what we had
 | |
|     // except for the first frame.
 | |
|     MOZ_ASSERT(mInsertIndex < mFrames.Length());
 | |
|     MOZ_ASSERT(!mFrames[mInsertIndex]);
 | |
|     MOZ_ASSERT(MayDiscard());
 | |
|     mFrames[mInsertIndex] = std::move(aFrame);
 | |
|   } else { // mInsertIndex == 0
 | |
|     // We were forced to restart an animation before we decoded the last
 | |
|     // frame. We don't need the redecoded first frame because we always keep
 | |
|     // the original.
 | |
|     MOZ_ASSERT(MayDiscard());
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(mFrames[mInsertIndex]);
 | |
|   ++mInsertIndex;
 | |
| 
 | |
|   // Ensure we only request more decoded frames if we actually need them. If we
 | |
|   // need to advance to a certain point in the animation on behalf of the owner,
 | |
|   // then do so. This ensures we keep decoding. If the batch size is really
 | |
|   // small (i.e. 1), it is possible advancing will request the decoder to
 | |
|   // "restart", but we haven't told it to stop yet. Note that we skip the first
 | |
|   // insert because we actually start "advanced" to the first frame anyways.
 | |
|   bool continueDecoding = --mPending > 0;
 | |
|   if (mAdvance > 0 && mInsertIndex > 1) {
 | |
|     continueDecoding |= AdvanceInternal();
 | |
|     --mAdvance;
 | |
|   }
 | |
|   return continueDecoding;
 | |
| }
 | |
| 
 | |
| bool
 | |
| AnimationFrameBuffer::MarkComplete()
 | |
| {
 | |
|   // We may have stopped decoding at a different point in the animation than we
 | |
|   // did previously. That means the decoder likely hit a new error, e.g. OOM.
 | |
|   // This will prevent us from advancing as well, because we are missing the
 | |
|   // required frames to blend.
 | |
|   //
 | |
|   // XXX(aosmond): In an ideal world, we would be generating full frames, and
 | |
|   // the consumer of our data doesn't care about our internal state. It simply
 | |
|   // knows about the first frame, the current frame, and how long to display the
 | |
|   // current frame.
 | |
|   if (NS_WARN_IF(mInsertIndex != mFrames.Length())) {
 | |
|     MOZ_ASSERT(mSizeKnown);
 | |
|     mRedecodeError = true;
 | |
|     mPending = 0;
 | |
|   }
 | |
| 
 | |
|   // We reached the end of the animation, the next frame we get, if we get
 | |
|   // another, will be the first frame again.
 | |
|   mInsertIndex = 0;
 | |
| 
 | |
|   // 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);
 | |
| 
 | |
|   if (!mSizeKnown) {
 | |
|     // We just received the last frame in the animation. Compact the frame array
 | |
|     // because we know we won't need to grow beyond here.
 | |
|     mSizeKnown = true;
 | |
|     mFrames.Compact();
 | |
| 
 | |
|     if (!MayDiscard()) {
 | |
|       // If we did not meet the threshold, then we know we want to keep all of the
 | |
|       // frames. If we also hit the last frame, we don't want to ask for more.
 | |
|       mPending = 0;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return mPending > 0;
 | |
| }
 | |
| 
 | |
| imgFrame*
 | |
| AnimationFrameBuffer::Get(size_t aFrame)
 | |
| {
 | |
|   // 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;
 | |
|   }
 | |
| 
 | |
|   // We've got the requested frame because we are not discarding frames. While
 | |
|   // we typically should have not run out of frames since we ask for more before
 | |
|   // we want them, it is possible the decoder is behind.
 | |
|   if (!mFrames[aFrame]) {
 | |
|     MOZ_ASSERT(MayDiscard());
 | |
|     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
 | |
| AnimationFrameBuffer::AdvanceTo(size_t aExpectedFrame)
 | |
| {
 | |
|   // The owner should only be advancing once it has reached the requested frame
 | |
|   // in the animation.
 | |
|   MOZ_ASSERT(mAdvance == 0);
 | |
|   bool restartDecoder = AdvanceInternal();
 | |
|   // Advancing should always be successful, as it should only happen after the
 | |
|   // owner has accessed the next (now current) frame.
 | |
|   MOZ_ASSERT(mGetIndex == aExpectedFrame);
 | |
|   return restartDecoder;
 | |
| }
 | |
| 
 | |
| bool
 | |
| AnimationFrameBuffer::AdvanceInternal()
 | |
| {
 | |
|   // We should not have advanced if we never inserted.
 | |
|   if (mFrames.IsEmpty()) {
 | |
|     MOZ_ASSERT_UNREACHABLE("Calling Advance() when we have no frames");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // 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(mFrames[mGetIndex]);
 | |
|   if (++mGetIndex == framesLength) {
 | |
|     MOZ_ASSERT(mSizeKnown);
 | |
|     mGetIndex = 0;
 | |
|   }
 | |
|   // The owner should have already accessed the next frame, so it should also
 | |
|   // be available.
 | |
|   MOZ_ASSERT(mFrames[mGetIndex]);
 | |
| 
 | |
|   // If we moved forward, that means we can remove the previous frame, assuming
 | |
|   // that frame is not the first frame. If we looped and are back at the first
 | |
|   // frame, we can remove the last frame.
 | |
|   if (MayDiscard()) {
 | |
|     RawAccessFrameRef discard;
 | |
|     if (mGetIndex > 1) {
 | |
|       discard = std::move(mFrames[mGetIndex - 1]);
 | |
|     } else if (mGetIndex == 0) {
 | |
|       MOZ_ASSERT(mSizeKnown && framesLength > 1);
 | |
|       discard = std::move(mFrames[framesLength - 1]);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!mRedecodeError && (!mSizeKnown || MayDiscard())) {
 | |
|     // Calculate how many frames we have requested ahead of the current frame.
 | |
|     size_t buffered = mPending;
 | |
|     if (mGetIndex > mInsertIndex) {
 | |
|       // It wrapped around and we are decoding the beginning again before the
 | |
|       // the display has finished the loop.
 | |
|       MOZ_ASSERT(mSizeKnown);
 | |
|       buffered += mInsertIndex + framesLength - mGetIndex - 1;
 | |
|     } else {
 | |
|       buffered += mInsertIndex - 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;
 | |
|       return mPending == mBatch;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool
 | |
| AnimationFrameBuffer::Reset()
 | |
| {
 | |
|   // The animation needs to start back at the beginning.
 | |
|   mGetIndex = 0;
 | |
|   mAdvance = 0;
 | |
| 
 | |
|   if (!MayDiscard()) {
 | |
|     // 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 && mInsertIndex - 1 >= mBatch * 2) {
 | |
|       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;
 | |
|   }
 | |
| 
 | |
|   // Discard all frames besides the first, because the decoder always expects
 | |
|   // that when it re-inserts a frame, it is not present. (It doesn't re-insert
 | |
|   // the first frame.)
 | |
|   for (size_t i = 1; i < mFrames.Length(); ++i) {
 | |
|     RawAccessFrameRef discard = std::move(mFrames[i]);
 | |
|   }
 | |
| 
 | |
|   mInsertIndex = 0;
 | |
| 
 | |
|   // If we hit an error after redecoding, we never want to restart decoding.
 | |
|   if (mRedecodeError) {
 | |
|     MOZ_ASSERT(mPending == 0);
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   bool restartDecoder = mPending == 0;
 | |
|   mPending = 2 * mBatch;
 | |
|   return restartDecoder;
 | |
| }
 | |
| 
 | |
| } // namespace image
 | |
| } // namespace mozilla
 |