summaryrefslogtreecommitdiff
path: root/image
diff options
context:
space:
mode:
authorMartok <martok@martoks-place.de>2022-12-31 22:55:46 +0100
committerMartok <martok@martoks-place.de>2023-01-04 00:49:04 +0100
commitb618b3fb767e61f73732a0385f733357571314b0 (patch)
treec7e53031c59f85d10ee167dc999696edc46eaa79 /image
parente7fd4ba61bec3736df2f50eb0e7b215a805dde06 (diff)
downloaduxp-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.cpp324
-rw-r--r--image/AnimationFrameBuffer.h204
-rw-r--r--image/AnimationSurfaceProvider.cpp237
-rw-r--r--image/AnimationSurfaceProvider.h17
-rw-r--r--image/DecodePool.cpp14
-rw-r--r--image/DecodePool.h4
-rw-r--r--image/Decoder.cpp4
-rw-r--r--image/Decoder.h20
-rw-r--r--image/DecoderFactory.cpp29
-rw-r--r--image/DecoderFactory.h13
-rw-r--r--image/FrameAnimator.cpp36
-rw-r--r--image/FrameAnimator.h6
-rw-r--r--image/ISurfaceProvider.h39
-rw-r--r--image/RasterImage.cpp10
-rw-r--r--image/SourceBuffer.h6
-rw-r--r--image/decoders/nsBMPDecoder.h2
-rw-r--r--image/decoders/nsGIFDecoder2.h2
-rw-r--r--image/decoders/nsICODecoder.h1
-rw-r--r--image/decoders/nsIconDecoder.h2
-rw-r--r--image/decoders/nsJPEGDecoder.h2
-rw-r--r--image/decoders/nsJXLDecoder.h2
-rw-r--r--image/decoders/nsPNGDecoder.h2
-rw-r--r--image/decoders/nsWebPDecoder.h2
-rw-r--r--image/moz.build1
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',