/* -*- 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] = 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(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 = 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] = 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; } DrawableFrameRef 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 DrawableFrameRef(); } // If we don't have that frame, return an empty frame ref. if (aFrame >= mFrames.Length()) { return DrawableFrameRef(); } // 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 DrawableFrameRef(); } // 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]->DrawableRef(); } 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 = Move(mFrames[mGetIndex - 1]); } else if (mGetIndex == 0) { MOZ_ASSERT(mSizeKnown && framesLength > 1); discard = 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 = 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