diff options
author | Martok <martok@martoks-place.de> | 2022-12-31 22:55:46 +0100 |
---|---|---|
committer | Martok <martok@martoks-place.de> | 2023-01-04 00:49:04 +0100 |
commit | b618b3fb767e61f73732a0385f733357571314b0 (patch) | |
tree | c7e53031c59f85d10ee167dc999696edc46eaa79 /image | |
parent | e7fd4ba61bec3736df2f50eb0e7b215a805dde06 (diff) | |
download | uxp-b618b3fb767e61f73732a0385f733357571314b0.tar.gz |
Issue #2073 - m-c 523950: Discard decoded frames of very large GIF animations (squashed)
Controlled by image.animated.decode-on-demand.threshold-kb, default 256MB
Includes squashed bugfixes/regressions:
- m-c 1444537: Shutting down the decode pool should make animated decoders bail early
- m-c 1628606: Make sure to mark the surface cache entry available before sending the frame complete notification
- m-c 1502275: Skip recreating the decoder after redecode errors if an animated image is reset
- m-c 1443232: Don't insert frames into our AnimationFrameBuffer that we consider in error and unusable
Diffstat (limited to 'image')
-rw-r--r-- | image/AnimationFrameBuffer.cpp | 324 | ||||
-rw-r--r-- | image/AnimationFrameBuffer.h | 204 | ||||
-rw-r--r-- | image/AnimationSurfaceProvider.cpp | 237 | ||||
-rw-r--r-- | image/AnimationSurfaceProvider.h | 17 | ||||
-rw-r--r-- | image/DecodePool.cpp | 14 | ||||
-rw-r--r-- | image/DecodePool.h | 4 | ||||
-rw-r--r-- | image/Decoder.cpp | 4 | ||||
-rw-r--r-- | image/Decoder.h | 20 | ||||
-rw-r--r-- | image/DecoderFactory.cpp | 29 | ||||
-rw-r--r-- | image/DecoderFactory.h | 13 | ||||
-rw-r--r-- | image/FrameAnimator.cpp | 36 | ||||
-rw-r--r-- | image/FrameAnimator.h | 6 | ||||
-rw-r--r-- | image/ISurfaceProvider.h | 39 | ||||
-rw-r--r-- | image/RasterImage.cpp | 10 | ||||
-rw-r--r-- | image/SourceBuffer.h | 6 | ||||
-rw-r--r-- | image/decoders/nsBMPDecoder.h | 2 | ||||
-rw-r--r-- | image/decoders/nsGIFDecoder2.h | 2 | ||||
-rw-r--r-- | image/decoders/nsICODecoder.h | 1 | ||||
-rw-r--r-- | image/decoders/nsIconDecoder.h | 2 | ||||
-rw-r--r-- | image/decoders/nsJPEGDecoder.h | 2 | ||||
-rw-r--r-- | image/decoders/nsJXLDecoder.h | 2 | ||||
-rw-r--r-- | image/decoders/nsPNGDecoder.h | 2 | ||||
-rw-r--r-- | image/decoders/nsWebPDecoder.h | 2 | ||||
-rw-r--r-- | image/moz.build | 1 |
24 files changed, 916 insertions, 63 deletions
diff --git a/image/AnimationFrameBuffer.cpp b/image/AnimationFrameBuffer.cpp new file mode 100644 index 0000000000..5d8b672f66 --- /dev/null +++ b/image/AnimationFrameBuffer.cpp @@ -0,0 +1,324 @@ +/* -*- 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 diff --git a/image/AnimationFrameBuffer.h b/image/AnimationFrameBuffer.h new file mode 100644 index 0000000000..aa23327e91 --- /dev/null +++ b/image/AnimationFrameBuffer.h @@ -0,0 +1,204 @@ +/* -*- 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/. */ + +#ifndef mozilla_image_AnimationFrameBuffer_h +#define mozilla_image_AnimationFrameBuffer_h + +#include "ISurfaceProvider.h" + +namespace mozilla { +namespace image { + +/** + * An AnimationFrameBuffer owns the frames outputted by an animated image + * decoder as well as directing its owner on how to drive the decoder, + * whether to produce more or to stop. + * + * Based upon its given configuration parameters, it will retain up to a + * certain number of frames in the buffer before deciding to discard previous + * frames, and relying upon the decoder to recreate older frames when the + * animation loops. It will also request that the decoder stop producing more + * frames when the display of the frames are far behind -- this allows other + * tasks and images which require decoding to take execution priority. + * + * The desire is that smaller animated images should be kept completely in + * memory while larger animated images should only keep a certain number of + * frames to minimize our memory footprint at the cost of CPU. + */ +class AnimationFrameBuffer final +{ +public: + AnimationFrameBuffer(); + + /** + * Configure the frame buffer with a particular threshold and batch size. Note + * that the frame buffer may adjust the given values. + * + * @param aThreshold Maximum number of frames that may be stored in the frame + * buffer before it may discard already displayed frames. + * Once exceeded, it will discard the previous frame to the + * current frame whenever Advance is called. It always + * retains the first frame. + * + * @param aBatch Number of frames we request to be decoded each time it + * decides we need more. + * + * @param aStartFrame The starting frame for the animation. The frame buffer + * will auto-advance (and thus keep the decoding pipeline + * going) until it has reached this frame. Useful when the + * animation was progressing, but the surface was + * discarded, and we had to redecode. + */ + void Initialize(size_t aThreshold, size_t aBatch, size_t aStartFrame); + + /** + * Access a specific frame from the frame buffer. It should generally access + * frames in sequential order, increasing in tandem with AdvanceTo calls. The + * first frame may be accessed at any time. The access order should start with + * the same value as that given in Initialize (aStartFrame). + * + * @param aFrame The frame index to access. + * + * @returns The frame, if available. + */ + DrawableFrameRef Get(size_t aFrame); + + /** + * Inserts a frame into the frame buffer. If it has yet to fully decode the + * animated image yet, then it will append the frame to its internal buffer. + * If it has been fully decoded, it will replace the next frame in its buffer + * with the given frame. + * + * Once we have a sufficient number of frames buffered relative to the + * currently displayed frame, it will return false to indicate the caller + * should stop decoding. + * + * @param aFrame The frame to insert into the buffer. + * + * @returns True if the decoder should decode another frame. + */ + bool Insert(RawAccessFrameRef&& aFrame); + + /** + * This should be called after the last frame has been inserted. If the buffer + * is discarding old frames, it may request more frames to be decoded. In this + * case that means the decoder should start again from the beginning. This + * return value should be used in preference to that of the Insert call. + * + * @returns True if the decoder should decode another frame. + */ + bool MarkComplete(); + + /** + * Advance the currently displayed frame of the frame buffer. If it reaches + * the end, it will loop back to the beginning. It should not be called unless + * a call to Get has returned a valid frame for the next frame index. + * + * As we advance, the number of frames we have buffered ahead of the current + * will shrink. Once that becomes too few, we will request a batch-sized set + * of frames to be decoded from the decoder. + * + * @param aExpectedFrame The frame we expect to have advanced to. This is + * used for confirmation purposes (e.g. asserts). + * + * @returns True if the caller should restart the decoder. + */ + bool AdvanceTo(size_t aExpectedFrame); + + /** + * Resets the currently displayed frame of the frame buffer to the beginning. + * If the buffer is discarding old frames, it will actually discard all frames + * besides the first. + * + * @returns True if the caller should restart the decoder. + */ + bool Reset(); + + /** + * @returns True if frames post-advance may be discarded and redecoded on + * demand, else false. + */ + bool MayDiscard() const { return mFrames.Length() > mThreshold; } + + /** + * @returns True if the frame buffer was ever marked as complete. This implies + * that the total number of frames is known and may be gotten from + * Frames().Length(). + */ + bool SizeKnown() const { return mSizeKnown; } + + /** + * @returns True if encountered an error during redecode which should cause + * the caller to stop inserting frames. + */ + bool HasRedecodeError() const { return mRedecodeError; } + + /** + * @returns The current frame index we have advanced to. + */ + size_t Displayed() const { return mGetIndex; } + + /** + * @returns Outstanding frames desired from the decoder. + */ + size_t PendingDecode() const { return mPending; } + + /** + * @returns Outstanding frames to advance internally. + */ + size_t PendingAdvance() const { return mAdvance; } + + /** + * @returns Number of frames we request to be decoded each time it decides we + * need more. + */ + size_t Batch() const { return mBatch; } + + /** + * @returns Maximum number of frames before we start discarding previous + * frames post-advance. + */ + size_t Threshold() const { return mThreshold; } + + /** + * @returns The frames of this animation, in order. May contain empty indices. + */ + const nsTArray<RawAccessFrameRef>& Frames() const { return mFrames; } + +private: + bool AdvanceInternal(); + + /// The frames of this animation, in order, but may have holes if discarding. + nsTArray<RawAccessFrameRef> mFrames; + + // The maximum number of frames we can have before discarding. + size_t mThreshold; + + // The minimum number of frames that we want buffered ahead of the display. + size_t mBatch; + + // The number of frames to decode before we stop. + size_t mPending; + + // The number of frames we need to auto-advance to synchronize with the caller. + size_t mAdvance; + + // The mFrames index in which to insert the next decoded frame. + size_t mInsertIndex; + + // The mFrames index that we have advanced to. + size_t mGetIndex; + + // True if the total number of frames is known. + bool mSizeKnown; + + // True if we encountered an error while redecoding. + bool mRedecodeError; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_AnimationFrameBuffer_h diff --git a/image/AnimationSurfaceProvider.cpp b/image/AnimationSurfaceProvider.cpp index 0dacf25c23..1d76332930 100644 --- a/image/AnimationSurfaceProvider.cpp +++ b/image/AnimationSurfaceProvider.cpp @@ -8,6 +8,7 @@ #include "gfxPrefs.h" #include "nsProxyRelease.h" +#include "DecodePool.h" #include "Decoder.h" using namespace mozilla::gfx; @@ -17,7 +18,8 @@ namespace image { AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull<RasterImage*> aImage, const SurfaceKey& aSurfaceKey, - NotNull<Decoder*> aDecoder) + NotNull<Decoder*> aDecoder, + size_t aCurrentFrame) : ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey, AvailabilityState::StartAsPlaceholder()) , mImage(aImage.get()) @@ -29,6 +31,22 @@ AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull<RasterImage*> aImage, "Use MetadataDecodingTask for metadata decodes"); MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(), "Use DecodedSurfaceProvider for single-frame image decodes"); + + // We still produce paletted surfaces for GIF which means the frames are + // smaller than one would expect for APNG. This may be removed if/when + // bug 1337111 lands and it is enabled by default. + size_t pixelSize = aDecoder->GetType() == DecoderType::GIF + ? sizeof(uint8_t) : sizeof(uint32_t); + + // Calculate how many frames we need to decode in this animation before we + // enter decode-on-demand mode. + IntSize frameSize = aSurfaceKey.Size(); + size_t threshold = + (size_t(gfxPrefs::ImageAnimatedDecodeOnDemandThresholdKB()) * 1024) / + (pixelSize * frameSize.width * frameSize.height); + size_t batch = gfxPrefs::ImageAnimatedDecodeOnDemandBatchSize(); + + mFrames.Initialize(threshold, batch, aCurrentFrame); } AnimationSurfaceProvider::~AnimationSurfaceProvider() @@ -53,6 +71,75 @@ AnimationSurfaceProvider::DropImageReference() NS_ReleaseOnMainThread(image.forget(), /* aAlwaysProxy = */ true); } +void +AnimationSurfaceProvider::Reset() +{ + // We want to go back to the beginning. + bool mayDiscard; + bool restartDecoder = false; + + { + MutexAutoLock lock(mFramesMutex); + + // If we have not crossed the threshold, we know we haven't discarded any + // frames, and thus we know it is safe move our display index back to the + // very beginning. It would be cleaner to let the frame buffer make this + // decision inside the AnimationFrameBuffer::Reset method, but if we have + // crossed the threshold, we need to hold onto the decoding mutex too. We + // should avoid blocking the main thread on the decoder threads. + mayDiscard = mFrames.MayDiscard(); + if (!mayDiscard) { + restartDecoder = mFrames.Reset(); + } + } + + if (mayDiscard) { + // We are over the threshold and have started discarding old frames. In + // that case we need to seize the decoding mutex. Thankfully we know that + // we are in the process of decoding at most the batch size frames, so + // this should not take too long to acquire. + MutexAutoLock lock(mDecodingMutex); + + // We may have hit an error while redecoding. Because FrameAnimator is + // tightly coupled to our own state, that means we would need to go through + // some heroics to resume animating in those cases. The typical reason for + // a redecode to fail is out of memory, and recycling should prevent most of + // those errors. When image.animated.generate-full-frames has shipped + // enabled on a release or two, we can simply remove the old FrameAnimator + // blending code and simplify this quite a bit -- just always pop the next + // full frame and timeout off the stack. + if (mDecoder) { + mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder); + MOZ_ASSERT(mDecoder); + + MutexAutoLock lock2(mFramesMutex); + restartDecoder = mFrames.Reset(); + } else { + MOZ_ASSERT(mFrames.HasRedecodeError()); + } + } + + if (restartDecoder) { + DecodePool::Singleton()->AsyncRun(this); + } +} + +void +AnimationSurfaceProvider::Advance(size_t aFrame) +{ + bool restartDecoder; + + { + // Typical advancement of a frame. + MutexAutoLock lock(mFramesMutex); + restartDecoder = mFrames.AdvanceTo(aFrame); + } + + if (restartDecoder) { + DecodePool::Singleton()->AsyncRun(this); + } +} + DrawableFrameRef AnimationSurfaceProvider::DrawableRef(size_t aFrame) { @@ -63,19 +150,7 @@ AnimationSurfaceProvider::DrawableRef(size_t aFrame) return DrawableFrameRef(); } - if (mFrames.IsEmpty()) { - MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() 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. Return it. - MOZ_ASSERT(mFrames[aFrame]); - return mFrames[aFrame]->DrawableRef(); + return mFrames.Get(aFrame); } bool @@ -88,13 +163,20 @@ AnimationSurfaceProvider::IsFinished() const return false; } - if (mFrames.IsEmpty()) { + if (mFrames.Frames().IsEmpty()) { MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no frames"); return false; } // As long as we have at least one finished frame, we're finished. - return mFrames[0]->IsFinished(); + return mFrames.Frames()[0]->IsFinished(); +} + +bool +AnimationSurfaceProvider::IsFullyDecoded() const +{ + MutexAutoLock lock(mFramesMutex); + return mFrames.SizeKnown() && !mFrames.MayDiscard(); } size_t @@ -125,8 +207,10 @@ AnimationSurfaceProvider::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, // that we must be careful to always use the same ordering elsewhere. MutexAutoLock lock(mFramesMutex); - for (const RawAccessFrameRef& frame : mFrames) { - frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut); + for (const RawAccessFrameRef& frame : mFrames.Frames()) { + if (frame) { + frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut); + } } } @@ -135,7 +219,7 @@ AnimationSurfaceProvider::Run() { MutexAutoLock lock(mDecodingMutex); - if (!mDecoder || !mImage) { + if (!mDecoder) { MOZ_ASSERT_UNREACHABLE("Running after decoding finished?"); return; } @@ -150,15 +234,34 @@ AnimationSurfaceProvider::Run() // Since we're not sure, rather than call CheckForNewFrameAtYield() here // we call CheckForNewFrameAtTerminalState(), which handles both of these // possibilities. - CheckForNewFrameAtTerminalState(); - - // We're done! + bool continueDecoding = CheckForNewFrameAtTerminalState(); FinishDecoding(); - return; + + // Even if it is the last frame, we may not have enough frames buffered + // ahead of the current. If we are shutting down, we want to ensure we + // release the thread as soon as possible. The animation may advance even + // during shutdown, which keeps us decoding, and thus blocking the decode + // pool during teardown. + if (!mDecoder || !continueDecoding || + DecodePool::Singleton()->IsShuttingDown()) { + return; + } + // Restart from the very beginning because the decoder was recreated. + continue; + } + + // If there is output available we want to change the entry in the surface + // cache from a placeholder to an actual surface now before NotifyProgress + // call below so that when consumers get the frame complete notification + // from the NotifyProgress they can actually get a surface from the surface + // cache. + bool checkForNewFrameAtYieldResult = false; + if (result == LexerResult(Yield::OUTPUT_AVAILABLE)) { + checkForNewFrameAtYieldResult = CheckForNewFrameAtYield(); } // Notify for the progress we've made so far. - if (mDecoder->HasProgress()) { + if (mImage && mDecoder->HasProgress()) { NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder)); } @@ -168,38 +271,52 @@ AnimationSurfaceProvider::Run() return; } - // There's new output available - a new frame! Grab it. + // There's new output available - a new frame! Grab it. If we don't need any + // more for the moment we can break out of the loop. If we are shutting + // down, we want to ensure we release the thread as soon as possible. The + // animation may advance even during shutdown, which keeps us decoding, and + // thus blocking the decode pool during teardown. MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE)); - CheckForNewFrameAtYield(); + if (!checkForNewFrameAtYieldResult || + DecodePool::Singleton()->IsShuttingDown()) { + return; + } } } -void +bool AnimationSurfaceProvider::CheckForNewFrameAtYield() { mDecodingMutex.AssertCurrentThreadOwns(); MOZ_ASSERT(mDecoder); bool justGotFirstFrame = false; + bool continueDecoding; { MutexAutoLock lock(mFramesMutex); // Try to get the new frame from the decoder. RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef(); + MOZ_ASSERT(mDecoder->HasFrameToTake()); + mDecoder->ClearHasFrameToTake(); + if (!frame) { MOZ_ASSERT_UNREACHABLE("Decoder yielded but didn't produce a frame?"); - return; + return true; } // We should've gotten a different frame than last time. - MOZ_ASSERT_IF(!mFrames.IsEmpty(), - mFrames.LastElement().get() != frame.get()); + MOZ_ASSERT_IF(!mFrames.Frames().IsEmpty(), + mFrames.Frames().LastElement().get() != frame.get()); // Append the new frame to the list. - mFrames.AppendElement(Move(frame)); + continueDecoding = mFrames.Insert(Move(frame)); - if (mFrames.Length() == 1) { + // We only want to handle the first frame if it is the first pass for the + // animation decoder. The owning image will be cleared after that. + size_t frameCount = mFrames.Frames().Length(); + if (frameCount == 1 && mImage) { justGotFirstFrame = true; } } @@ -207,32 +324,49 @@ AnimationSurfaceProvider::CheckForNewFrameAtYield() if (justGotFirstFrame) { AnnounceSurfaceAvailable(); } + + return continueDecoding; } -void +bool AnimationSurfaceProvider::CheckForNewFrameAtTerminalState() { mDecodingMutex.AssertCurrentThreadOwns(); MOZ_ASSERT(mDecoder); bool justGotFirstFrame = false; + bool continueDecoding; { MutexAutoLock lock(mFramesMutex); + // The decoder may or may not have a new frame for us at this point. Avoid + // reinserting the same frame again. RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef(); - if (!frame) { - return; + + // If the decoder didn't finish a new frame (ie if, after starting the + // frame, it got an error and aborted the frame and the rest of the decode) + // that means it won't be reporting it to the image or FrameAnimator so we + // should ignore it too, that's what HasFrameToTake tracks basically. + if (!mDecoder->HasFrameToTake()) { + frame = RawAccessFrameRef(); + } else { + MOZ_ASSERT(frame); + mDecoder->ClearHasFrameToTake(); } - if (!mFrames.IsEmpty() && mFrames.LastElement().get() == frame.get()) { - return; // We already have this one. + if (!frame || (!mFrames.Frames().IsEmpty() && + mFrames.Frames().LastElement().get() == frame.get())) { + return mFrames.MarkComplete(); } // Append the new frame to the list. - mFrames.AppendElement(Move(frame)); + mFrames.Insert(Move(frame)); + continueDecoding = mFrames.MarkComplete(); - if (mFrames.Length() == 1) { + // We only want to handle the first frame if it is the first pass for the + // animation decoder. The owning image will be cleared after that. + if (mFrames.Frames().Length() == 1 && mImage) { justGotFirstFrame = true; } } @@ -240,6 +374,8 @@ AnimationSurfaceProvider::CheckForNewFrameAtTerminalState() if (justGotFirstFrame) { AnnounceSurfaceAvailable(); } + + return continueDecoding; } void @@ -260,14 +396,27 @@ void AnimationSurfaceProvider::FinishDecoding() { mDecodingMutex.AssertCurrentThreadOwns(); - MOZ_ASSERT(mImage); MOZ_ASSERT(mDecoder); - // Send notifications. - NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder)); + if (mImage) { + // Send notifications. + NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder)); + } - // Destroy our decoder; we don't need it anymore. - mDecoder = nullptr; + // Determine if we need to recreate the decoder, in case we are discarding + // frames and need to loop back to the beginning. + bool recreateDecoder; + { + MutexAutoLock lock(mFramesMutex); + recreateDecoder = !mFrames.HasRedecodeError() && mFrames.MayDiscard(); + } + + if (recreateDecoder) { + mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder); + MOZ_ASSERT(mDecoder); + } else { + mDecoder = nullptr; + } // We don't need a reference to our image anymore, either, and we don't want // one. We may be stored in the surface cache for a long time after decoding diff --git a/image/AnimationSurfaceProvider.h b/image/AnimationSurfaceProvider.h index bf87f37ac4..720cae57e7 100644 --- a/image/AnimationSurfaceProvider.h +++ b/image/AnimationSurfaceProvider.h @@ -13,6 +13,7 @@ #include "FrameAnimator.h" #include "IDecodingTask.h" #include "ISurfaceProvider.h" +#include "AnimationFrameBuffer.h" namespace mozilla { namespace image { @@ -31,7 +32,8 @@ public: AnimationSurfaceProvider(NotNull<RasterImage*> aImage, const SurfaceKey& aSurfaceKey, - NotNull<Decoder*> aDecoder); + NotNull<Decoder*> aDecoder, + size_t aCurrentFrame); ////////////////////////////////////////////////////////////////////////////// @@ -44,10 +46,13 @@ public: DrawableSurface Surface() override { return DrawableSurface(WrapNotNull(this)); } bool IsFinished() const override; + bool IsFullyDecoded() const override; size_t LogicalSizeInBytes() const override; void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, size_t& aHeapSizeOut, size_t& aNonHeapSizeOut) override; + void Reset() override; + void Advance(size_t aFrame) override; protected: DrawableFrameRef DrawableRef(size_t aFrame) override; @@ -77,11 +82,15 @@ private: virtual ~AnimationSurfaceProvider(); void DropImageReference(); - void CheckForNewFrameAtYield(); - void CheckForNewFrameAtTerminalState(); void AnnounceSurfaceAvailable(); void FinishDecoding(); + // @returns Whether or not we should continue decoding. + bool CheckForNewFrameAtYield(); + + // @returns Whether or not we should restart decoding. + bool CheckForNewFrameAtTerminalState(); + /// The image associated with our decoder. RefPtr<RasterImage> mImage; @@ -95,7 +104,7 @@ private: mutable Mutex mFramesMutex; /// The frames of this animation, in order. - nsTArray<RawAccessFrameRef> mFrames; + AnimationFrameBuffer mFrames; }; } // namespace image diff --git a/image/DecodePool.cpp b/image/DecodePool.cpp index a8c4cbecc0..0aeed61547 100644 --- a/image/DecodePool.cpp +++ b/image/DecodePool.cpp @@ -87,6 +87,12 @@ public: mMonitor.NotifyAll(); } + bool IsShuttingDown() const + { + MonitorAutoLock lock(mMonitor); + return mShuttingDown; + } + /// Pushes a new decode work item. void PushWork(IDecodingTask* aTask) { @@ -150,7 +156,7 @@ private: nsThreadPoolNaming mThreadNaming; // mMonitor guards the queues and mShuttingDown. - Monitor mMonitor; + mutable Monitor mMonitor; nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue; nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue; bool mShuttingDown; @@ -299,6 +305,12 @@ DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*) return NS_OK; } +bool +DecodePool::IsShuttingDown() const +{ + return mImpl->IsShuttingDown(); +} + void DecodePool::AsyncRun(IDecodingTask* aTask) { diff --git a/image/DecodePool.h b/image/DecodePool.h index 9d62731e50..fd56f0bfa1 100644 --- a/image/DecodePool.h +++ b/image/DecodePool.h @@ -54,6 +54,10 @@ public: /// same as the number of decoding threads we're actually using. static uint32_t NumberOfCores(); + /// True if the DecodePool is being shutdown. This may only be called by + /// threads from the pool to check if they should keep working or not. + bool IsShuttingDown() const; + /// Ask the DecodePool to run @aTask asynchronously and return immediately. void AsyncRun(IDecodingTask* aTask); diff --git a/image/Decoder.cpp b/image/Decoder.cpp index 5d39080928..9d0647a4a3 100644 --- a/image/Decoder.cpp +++ b/image/Decoder.cpp @@ -36,6 +36,7 @@ Decoder::Decoder(RasterImage* aImage) , mHaveExplicitOutputSize(false) , mInFrame(false) , mFinishedNewFrame(false) + , mHasFrameToTake(false) , mReachedTerminalState(false) , mDecodeDone(false) , mError(false) @@ -254,6 +255,8 @@ Decoder::AllocateFrame(const gfx::IntSize& aOutputSize, mCurrentFrame.get()); if (mCurrentFrame) { + mHasFrameToTake = true; + // Gather the raw pointers the decoders will use. mCurrentFrame->GetImageData(&mImageData, &mImageDataLength); mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize); @@ -474,6 +477,7 @@ Decoder::PostError() mCurrentFrame->Abort(); mInFrame = false; --mFrameCount; + mHasFrameToTake = false; } } diff --git a/image/Decoder.h b/image/Decoder.h index c0f4a20a67..87dfb00508 100644 --- a/image/Decoder.h +++ b/image/Decoder.h @@ -200,6 +200,11 @@ public: mIterator.emplace(Move(aIterator)); } + SourceBuffer* GetSourceBuffer() const + { + return mIterator->Owner(); + } + /** * Should this decoder send partial invalidations? */ @@ -244,6 +249,12 @@ public: /// Are we in the middle of a frame right now? Used for assertions only. bool InFrame() const { return mInFrame; } + /// Type of decoder. + virtual DecoderType GetType() const + { + return DecoderType::UNKNOWN; + } + enum DecodeStyle { PROGRESSIVE, // produce intermediate frames representing the partial // state of the image @@ -339,6 +350,11 @@ public: : RawAccessFrameRef(); } + bool HasFrameToTake() const { return mHasFrameToTake; } + void ClearHasFrameToTake() { + MOZ_ASSERT(mHasFrameToTake); + mHasFrameToTake = false; + } protected: friend class nsICODecoder; @@ -493,6 +509,10 @@ private: bool mInFrame : 1; bool mFinishedNewFrame : 1; // True if PostFrameStop() has been called since // the last call to TakeCompleteFrameCount(). + // Has a new frame that AnimationSurfaceProvider can take. Unfortunately this + // has to be separate from mFinishedNewFrame because the png decoder yields a + // new frame before calling PostFrameStop(). + bool mHasFrameToTake : 1; bool mReachedTerminalState : 1; bool mDecodeDone : 1; bool mError : 1; diff --git a/image/DecoderFactory.cpp b/image/DecoderFactory.cpp index 3f8f64377a..8acffac835 100644 --- a/image/DecoderFactory.cpp +++ b/image/DecoderFactory.cpp @@ -195,7 +195,8 @@ DecoderFactory::CreateAnimationDecoder(DecoderType aType, NotNull<SourceBuffer*> aSourceBuffer, const IntSize& aIntrinsicSize, DecoderFlags aDecoderFlags, - SurfaceFlags aSurfaceFlags) + SurfaceFlags aSurfaceFlags, + size_t aCurrentFrame) { if (aType == DecoderType::UNKNOWN) { return nullptr; @@ -230,7 +231,8 @@ DecoderFactory::CreateAnimationDecoder(DecoderType aType, NotNull<RefPtr<AnimationSurfaceProvider>> provider = WrapNotNull(new AnimationSurfaceProvider(aImage, surfaceKey, - WrapNotNull(decoder))); + WrapNotNull(decoder), + aCurrentFrame)); // Attempt to insert the surface provider into the surface cache right away so // we won't trigger any more decoders with the same parameters. @@ -243,6 +245,29 @@ DecoderFactory::CreateAnimationDecoder(DecoderType aType, return task.forget(); } +/* static */ already_AddRefed<Decoder> +DecoderFactory::CloneAnimationDecoder(Decoder* aDecoder) +{ + MOZ_ASSERT(aDecoder); + MOZ_ASSERT(aDecoder->HasAnimation()); + + RefPtr<Decoder> decoder = GetDecoder(aDecoder->GetType(), nullptr, + /* aIsRedecode = */ true); + MOZ_ASSERT(decoder, "Should have a decoder now"); + + // Initialize the decoder. + decoder->SetMetadataDecode(false); + decoder->SetIterator(aDecoder->GetSourceBuffer()->Iterator()); + decoder->SetDecoderFlags(aDecoder->GetDecoderFlags()); + decoder->SetSurfaceFlags(aDecoder->GetSurfaceFlags()); + + if (NS_FAILED(decoder->Init())) { + return nullptr; + } + + return decoder.forget(); +} + /* static */ already_AddRefed<IDecodingTask> DecoderFactory::CreateMetadataDecoder(DecoderType aType, NotNull<RasterImage*> aImage, diff --git a/image/DecoderFactory.h b/image/DecoderFactory.h index 0860a6ace5..4267b1f809 100644 --- a/image/DecoderFactory.h +++ b/image/DecoderFactory.h @@ -93,6 +93,7 @@ public: * @param aDecoderFlags Flags specifying the behavior of this decoder. * @param aSurfaceFlags Flags specifying the type of output this decoder * should produce. + * @param aCurrentFrame The current frame the decoder should auto advance to. */ static already_AddRefed<IDecodingTask> CreateAnimationDecoder(DecoderType aType, @@ -100,7 +101,17 @@ public: NotNull<SourceBuffer*> aSourceBuffer, const gfx::IntSize& aIntrinsicSize, DecoderFlags aDecoderFlags, - SurfaceFlags aSurfaceFlags); + SurfaceFlags aSurfaceFlags, + size_t aCurrentFrame); + + /** + * Creates and initializes a decoder for animated images, cloned from the + * given decoder. + * + * @param aDecoder Decoder to clone. + */ + static already_AddRefed<Decoder> + CloneAnimationDecoder(Decoder* aDecoder); /** * Creates and initializes a metadata decoder of type @aType. This decoder diff --git a/image/FrameAnimator.cpp b/image/FrameAnimator.cpp index 066da94f40..b9a4aec4a8 100644 --- a/image/FrameAnimator.cpp +++ b/image/FrameAnimator.cpp @@ -64,12 +64,7 @@ AnimationState::UpdateStateInternal(LookupResult& aResult, if (mHasBeenDecoded) { Maybe<uint32_t> frameCount = FrameCount(); MOZ_ASSERT(frameCount.isSome()); - aResult.Surface().Seek(*frameCount - 1); - if (aResult.Surface() && aResult.Surface()->IsFinished()) { - mIsCurrentlyDecoded = true; - } else { - mIsCurrentlyDecoded = false; - } + mIsCurrentlyDecoded = aResult.Surface().IsFullyDecoded(); } } @@ -286,8 +281,12 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, // failure) we would have discarded all the old frames and may not yet have // the new ones. if (!nextFrame || !nextFrame->IsFinished()) { - // Uh oh, the frame we want to show is currently being decoded (partial) - // Wait until the next refresh driver tick and try again + // Uh oh, the frame we want to show is currently being decoded (partial). + // Similar to the above case, we could be blocked by network or decoding, + // and so we should advance our current time rather than risk jumping + // through the animation. We will wait until the next refresh driver tick + // and try again. + aState.mCurrentAnimationFrameTime = aTime; return ret; } @@ -313,6 +312,7 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, MOZ_ASSERT(currentFrameEndTime.isSome()); aState.mCurrentAnimationFrameTime = *currentFrameEndTime; aState.mCurrentAnimationFrameIndex = nextFrameIndex; + aFrames.Advance(nextFrameIndex); return ret; } @@ -343,6 +343,7 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, // Set currentAnimationFrameIndex at the last possible moment aState.mCurrentAnimationFrameIndex = nextFrameIndex; + aFrames.Advance(nextFrameIndex); // If we're here, we successfully advanced the frame. ret.mFrameAdvanced = true; @@ -350,6 +351,25 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, return ret; } +void +FrameAnimator::ResetAnimation(AnimationState& aState) +{ + aState.ResetAnimation(); + + // Our surface provider is synchronized to our state, so we need to reset its + // state as well, if we still have one. + LookupResult result = + SurfaceCache::Lookup(ImageKey(mImage), + RasterSurfaceKey(mSize, + DefaultSurfaceFlags(), + PlaybackType::eAnimated)); + if (!result) { + return; + } + + result.Surface().Reset(); +} + RefreshResult FrameAnimator::RequestRefresh(AnimationState& aState, const TimeStamp& aTime, diff --git a/image/FrameAnimator.h b/image/FrameAnimator.h index 44b5a52e7c..fd331da9eb 100644 --- a/image/FrameAnimator.h +++ b/image/FrameAnimator.h @@ -277,6 +277,12 @@ public: } /** + * Call when you need to re-start animating. Ensures we start from the first + * frame. + */ + void ResetAnimation(AnimationState& aState); + + /** * Re-evaluate what frame we're supposed to be on, and do whatever blending * is necessary to get us to that frame. * diff --git a/image/ISurfaceProvider.h b/image/ISurfaceProvider.h index 80e1f8e9b0..b4aaf89f9b 100644 --- a/image/ISurfaceProvider.h +++ b/image/ISurfaceProvider.h @@ -54,6 +54,12 @@ public: /// @return true if DrawableRef() will return a completely decoded surface. virtual bool IsFinished() const = 0; + /// @return true if the underlying decoder is currently fully decoded. For + /// animated images, this means that at least every frame has been decoded + /// at least once. It does not guarantee that all of the frames are present, + /// as the surface provider has the option to discard as it deems necessary. + virtual bool IsFullyDecoded() const { return IsFinished(); } + /// @return the number of bytes of memory this ISurfaceProvider is expected to /// require. Optimizations may result in lower real memory usage. Trivial /// overhead is ignored. Because this value is used in bookkeeping, it's @@ -75,6 +81,9 @@ public: ref->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut); } + virtual void Reset() { } + virtual void Advance(size_t aFrame) { } + /// @return the availability state of this ISurfaceProvider, which indicates /// whether DrawableRef() could successfully return a surface. Should only be /// called from SurfaceCache code as it relies on SurfaceCache for @@ -189,6 +198,36 @@ public: return mDrawableRef ? NS_OK : NS_ERROR_FAILURE; } + void Reset() + { + if (!mProvider) { + MOZ_ASSERT_UNREACHABLE("Trying to reset a static DrawableSurface?"); + return; + } + + mProvider->Reset(); + } + + void Advance(size_t aFrame) + { + if (!mProvider) { + MOZ_ASSERT_UNREACHABLE("Trying to advance a static DrawableSurface?"); + return; + } + + mProvider->Advance(aFrame); + } + + bool IsFullyDecoded() const + { + if (!mProvider) { + MOZ_ASSERT_UNREACHABLE("Trying to check decoding state of a static DrawableSurface?"); + return false; + } + + return mProvider->IsFullyDecoded(); + } + explicit operator bool() const { return mHaveSurface; } imgFrame* operator->() { return DrawableRef().get(); } diff --git a/image/RasterImage.cpp b/image/RasterImage.cpp index f7dfd0bb1a..4c6cce891b 100644 --- a/image/RasterImage.cpp +++ b/image/RasterImage.cpp @@ -853,7 +853,8 @@ RasterImage::ResetAnimation() } MOZ_ASSERT(mAnimationState, "Should have AnimationState"); - mAnimationState->ResetAnimation(); + MOZ_ASSERT(mFrameAnimator, "Should have FrameAnimator"); + mFrameAnimator->ResetAnimation(*mAnimationState); NotifyProgress(NoProgress, mAnimationState->FirstFrameRefreshArea()); @@ -1183,10 +1184,13 @@ RasterImage::Decode(const IntSize& aSize, // Create a decoder. RefPtr<IDecodingTask> task; - if (mAnimationState && aPlaybackType == PlaybackType::eAnimated) { + bool animated = mAnimationState && aPlaybackType == PlaybackType::eAnimated; + if (animated) { + size_t currentFrame = mAnimationState->GetCurrentAnimationFrameIndex(); task = DecoderFactory::CreateAnimationDecoder(mDecoderType, WrapNotNull(this), mSourceBuffer, mSize, - decoderFlags, surfaceFlags); + decoderFlags, surfaceFlags, + currentFrame); mAnimationState->UpdateState(mAnimationFinished, this, mSize); // If the animation is finished we can draw right away because we just draw // the final frame all the time from now on. See comment in diff --git a/image/SourceBuffer.h b/image/SourceBuffer.h index 6f2c74d33b..e5aff1dcdd 100644 --- a/image/SourceBuffer.h +++ b/image/SourceBuffer.h @@ -187,6 +187,12 @@ public: /// @return a count of the bytes in all chunks we've advanced through. size_t ByteCount() const { return mByteCount; } + /// @return the source buffer which owns the iterator. + SourceBuffer* Owner() const { + MOZ_ASSERT(mOwner); + return mOwner; + } + private: friend class SourceBuffer; diff --git a/image/decoders/nsBMPDecoder.h b/image/decoders/nsBMPDecoder.h index 26724d44e6..793ebd1558 100644 --- a/image/decoders/nsBMPDecoder.h +++ b/image/decoders/nsBMPDecoder.h @@ -124,6 +124,8 @@ class nsBMPDecoder : public Decoder public: ~nsBMPDecoder(); + DecoderType GetType() const override { return DecoderType::BMP; } + /// Obtains the internal output image buffer. uint32_t* GetImageData() { return reinterpret_cast<uint32_t*>(mImageData); } diff --git a/image/decoders/nsGIFDecoder2.h b/image/decoders/nsGIFDecoder2.h index 46ceaa4e94..235bdf7c3d 100644 --- a/image/decoders/nsGIFDecoder2.h +++ b/image/decoders/nsGIFDecoder2.h @@ -24,6 +24,8 @@ class nsGIFDecoder2 : public Decoder public: ~nsGIFDecoder2(); + DecoderType GetType() const override { return DecoderType::GIF; } + protected: LexerResult DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) override; diff --git a/image/decoders/nsICODecoder.h b/image/decoders/nsICODecoder.h index 46e1377aad..e33550fc54 100644 --- a/image/decoders/nsICODecoder.h +++ b/image/decoders/nsICODecoder.h @@ -69,6 +69,7 @@ public: /// @return The offset from the beginning of the ICO to the first resource. size_t FirstResourceOffset() const; + DecoderType GetType() const override { return DecoderType::ICO; } LexerResult DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) override; nsresult FinishInternal() override; diff --git a/image/decoders/nsIconDecoder.h b/image/decoders/nsIconDecoder.h index 69198315b1..82402f305a 100644 --- a/image/decoders/nsIconDecoder.h +++ b/image/decoders/nsIconDecoder.h @@ -37,6 +37,8 @@ class nsIconDecoder : public Decoder public: virtual ~nsIconDecoder(); + DecoderType GetType() const override { return DecoderType::ICON; } + LexerResult DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) override; diff --git a/image/decoders/nsJPEGDecoder.h b/image/decoders/nsJPEGDecoder.h index 7df89318c9..25177cef91 100644 --- a/image/decoders/nsJPEGDecoder.h +++ b/image/decoders/nsJPEGDecoder.h @@ -52,6 +52,8 @@ class nsJPEGDecoder : public Decoder public: virtual ~nsJPEGDecoder(); + DecoderType GetType() const override { return DecoderType::JPEG; } + virtual void SetSampleSize(int aSampleSize) override { mSampleSize = aSampleSize; diff --git a/image/decoders/nsJXLDecoder.h b/image/decoders/nsJXLDecoder.h index 02b7481bd9..e4d43bbfe7 100644 --- a/image/decoders/nsJXLDecoder.h +++ b/image/decoders/nsJXLDecoder.h @@ -21,6 +21,8 @@ class nsJXLDecoder final : public Decoder { public: virtual ~nsJXLDecoder(); + DecoderType GetType() const override { return DecoderType::JXL; } + protected: LexerResult DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) override; diff --git a/image/decoders/nsPNGDecoder.h b/image/decoders/nsPNGDecoder.h index 7e677d40ab..c4ec93227f 100644 --- a/image/decoders/nsPNGDecoder.h +++ b/image/decoders/nsPNGDecoder.h @@ -25,6 +25,8 @@ public: /// @return true if this PNG is a valid ICO resource. bool IsValidICO() const; + DecoderType GetType() const override { return DecoderType::PNG; } + protected: nsresult InitInternal() override; LexerResult DoDecode(SourceBufferIterator& aIterator, diff --git a/image/decoders/nsWebPDecoder.h b/image/decoders/nsWebPDecoder.h index cdd2849f30..21df5279b6 100644 --- a/image/decoders/nsWebPDecoder.h +++ b/image/decoders/nsWebPDecoder.h @@ -21,6 +21,8 @@ class nsWebPDecoder final : public Decoder public: virtual ~nsWebPDecoder(); + DecoderType GetType() const override { return DecoderType::WEBP; } + protected: LexerResult DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume) override; diff --git a/image/moz.build b/image/moz.build index b73c5ae519..04582e9ef8 100644 --- a/image/moz.build +++ b/image/moz.build @@ -47,6 +47,7 @@ EXPORTS += [ ] UNIFIED_SOURCES += [ + 'AnimationFrameBuffer.cpp', 'AnimationSurfaceProvider.cpp', 'ClippedImage.cpp', 'DecodedSurfaceProvider.cpp', |