mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-04 22:37:29 +09:00
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
This commit is contained in:
parent
eac8afce35
commit
e96122ede2
25 changed files with 925 additions and 63 deletions
|
|
@ -429,6 +429,8 @@ private:
|
||||||
#endif
|
#endif
|
||||||
DECL_GFX_PREF(Live, "gl.require-hardware", RequireHardwareGL, bool, false);
|
DECL_GFX_PREF(Live, "gl.require-hardware", RequireHardwareGL, bool, false);
|
||||||
|
|
||||||
|
DECL_GFX_PREF(Live, "image.animated.decode-on-demand.threshold-kb", ImageAnimatedDecodeOnDemandThresholdKB, uint32_t, 256*1024);
|
||||||
|
DECL_GFX_PREF(Live, "image.animated.decode-on-demand.batch-size", ImageAnimatedDecodeOnDemandBatchSize, uint32_t, 6);
|
||||||
DECL_GFX_PREF(Once, "image.cache.size", ImageCacheSize, int32_t, 5*1024*1024);
|
DECL_GFX_PREF(Once, "image.cache.size", ImageCacheSize, int32_t, 5*1024*1024);
|
||||||
DECL_GFX_PREF(Once, "image.cache.timeweight", ImageCacheTimeWeight, int32_t, 500);
|
DECL_GFX_PREF(Once, "image.cache.timeweight", ImageCacheTimeWeight, int32_t, 500);
|
||||||
DECL_GFX_PREF(Live, "image.decode-immediately.enabled", ImageDecodeImmediatelyEnabled, bool, false);
|
DECL_GFX_PREF(Live, "image.decode-immediately.enabled", ImageDecodeImmediatelyEnabled, bool, false);
|
||||||
|
|
|
||||||
324
image/AnimationFrameBuffer.cpp
Normal file
324
image/AnimationFrameBuffer.cpp
Normal file
|
|
@ -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
|
||||||
204
image/AnimationFrameBuffer.h
Normal file
204
image/AnimationFrameBuffer.h
Normal file
|
|
@ -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
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include "gfxPrefs.h"
|
#include "gfxPrefs.h"
|
||||||
#include "nsProxyRelease.h"
|
#include "nsProxyRelease.h"
|
||||||
|
|
||||||
|
#include "DecodePool.h"
|
||||||
#include "Decoder.h"
|
#include "Decoder.h"
|
||||||
|
|
||||||
using namespace mozilla::gfx;
|
using namespace mozilla::gfx;
|
||||||
|
|
@ -17,7 +18,8 @@ namespace image {
|
||||||
|
|
||||||
AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
|
AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
|
||||||
const SurfaceKey& aSurfaceKey,
|
const SurfaceKey& aSurfaceKey,
|
||||||
NotNull<Decoder*> aDecoder)
|
NotNull<Decoder*> aDecoder,
|
||||||
|
size_t aCurrentFrame)
|
||||||
: ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey,
|
: ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey,
|
||||||
AvailabilityState::StartAsPlaceholder())
|
AvailabilityState::StartAsPlaceholder())
|
||||||
, mImage(aImage.get())
|
, mImage(aImage.get())
|
||||||
|
|
@ -29,6 +31,22 @@ AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
|
||||||
"Use MetadataDecodingTask for metadata decodes");
|
"Use MetadataDecodingTask for metadata decodes");
|
||||||
MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(),
|
MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(),
|
||||||
"Use DecodedSurfaceProvider for single-frame image decodes");
|
"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()
|
AnimationSurfaceProvider::~AnimationSurfaceProvider()
|
||||||
|
|
@ -53,6 +71,75 @@ AnimationSurfaceProvider::DropImageReference()
|
||||||
NS_ReleaseOnMainThread(image.forget(), /* aAlwaysProxy = */ true);
|
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
|
DrawableFrameRef
|
||||||
AnimationSurfaceProvider::DrawableRef(size_t aFrame)
|
AnimationSurfaceProvider::DrawableRef(size_t aFrame)
|
||||||
{
|
{
|
||||||
|
|
@ -63,19 +150,7 @@ AnimationSurfaceProvider::DrawableRef(size_t aFrame)
|
||||||
return DrawableFrameRef();
|
return DrawableFrameRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mFrames.IsEmpty()) {
|
return mFrames.Get(aFrame);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|
@ -88,13 +163,20 @@ AnimationSurfaceProvider::IsFinished() const
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mFrames.IsEmpty()) {
|
if (mFrames.Frames().IsEmpty()) {
|
||||||
MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no frames");
|
MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no frames");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// As long as we have at least one finished frame, we're finished.
|
// 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
|
size_t
|
||||||
|
|
@ -125,17 +207,19 @@ AnimationSurfaceProvider::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
|
||||||
// that we must be careful to always use the same ordering elsewhere.
|
// that we must be careful to always use the same ordering elsewhere.
|
||||||
MutexAutoLock lock(mFramesMutex);
|
MutexAutoLock lock(mFramesMutex);
|
||||||
|
|
||||||
for (const RawAccessFrameRef& frame : mFrames) {
|
for (const RawAccessFrameRef& frame : mFrames.Frames()) {
|
||||||
|
if (frame) {
|
||||||
frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut);
|
frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AnimationSurfaceProvider::Run()
|
AnimationSurfaceProvider::Run()
|
||||||
{
|
{
|
||||||
MutexAutoLock lock(mDecodingMutex);
|
MutexAutoLock lock(mDecodingMutex);
|
||||||
|
|
||||||
if (!mDecoder || !mImage) {
|
if (!mDecoder) {
|
||||||
MOZ_ASSERT_UNREACHABLE("Running after decoding finished?");
|
MOZ_ASSERT_UNREACHABLE("Running after decoding finished?");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -150,15 +234,34 @@ AnimationSurfaceProvider::Run()
|
||||||
// Since we're not sure, rather than call CheckForNewFrameAtYield() here
|
// Since we're not sure, rather than call CheckForNewFrameAtYield() here
|
||||||
// we call CheckForNewFrameAtTerminalState(), which handles both of these
|
// we call CheckForNewFrameAtTerminalState(), which handles both of these
|
||||||
// possibilities.
|
// possibilities.
|
||||||
CheckForNewFrameAtTerminalState();
|
bool continueDecoding = CheckForNewFrameAtTerminalState();
|
||||||
|
|
||||||
// We're done!
|
|
||||||
FinishDecoding();
|
FinishDecoding();
|
||||||
|
|
||||||
|
// 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;
|
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.
|
// Notify for the progress we've made so far.
|
||||||
if (mDecoder->HasProgress()) {
|
if (mImage && mDecoder->HasProgress()) {
|
||||||
NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder));
|
NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,38 +271,52 @@ AnimationSurfaceProvider::Run()
|
||||||
return;
|
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));
|
MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE));
|
||||||
CheckForNewFrameAtYield();
|
if (!checkForNewFrameAtYieldResult ||
|
||||||
|
DecodePool::Singleton()->IsShuttingDown()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
AnimationSurfaceProvider::CheckForNewFrameAtYield()
|
AnimationSurfaceProvider::CheckForNewFrameAtYield()
|
||||||
{
|
{
|
||||||
mDecodingMutex.AssertCurrentThreadOwns();
|
mDecodingMutex.AssertCurrentThreadOwns();
|
||||||
MOZ_ASSERT(mDecoder);
|
MOZ_ASSERT(mDecoder);
|
||||||
|
|
||||||
bool justGotFirstFrame = false;
|
bool justGotFirstFrame = false;
|
||||||
|
bool continueDecoding;
|
||||||
|
|
||||||
{
|
{
|
||||||
MutexAutoLock lock(mFramesMutex);
|
MutexAutoLock lock(mFramesMutex);
|
||||||
|
|
||||||
// Try to get the new frame from the decoder.
|
// Try to get the new frame from the decoder.
|
||||||
RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
|
RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
|
||||||
|
MOZ_ASSERT(mDecoder->HasFrameToTake());
|
||||||
|
mDecoder->ClearHasFrameToTake();
|
||||||
|
|
||||||
if (!frame) {
|
if (!frame) {
|
||||||
MOZ_ASSERT_UNREACHABLE("Decoder yielded but didn't produce a 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.
|
// We should've gotten a different frame than last time.
|
||||||
MOZ_ASSERT_IF(!mFrames.IsEmpty(),
|
MOZ_ASSERT_IF(!mFrames.Frames().IsEmpty(),
|
||||||
mFrames.LastElement().get() != frame.get());
|
mFrames.Frames().LastElement().get() != frame.get());
|
||||||
|
|
||||||
// Append the new frame to the list.
|
// 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;
|
justGotFirstFrame = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -207,32 +324,49 @@ AnimationSurfaceProvider::CheckForNewFrameAtYield()
|
||||||
if (justGotFirstFrame) {
|
if (justGotFirstFrame) {
|
||||||
AnnounceSurfaceAvailable();
|
AnnounceSurfaceAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return continueDecoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
AnimationSurfaceProvider::CheckForNewFrameAtTerminalState()
|
AnimationSurfaceProvider::CheckForNewFrameAtTerminalState()
|
||||||
{
|
{
|
||||||
mDecodingMutex.AssertCurrentThreadOwns();
|
mDecodingMutex.AssertCurrentThreadOwns();
|
||||||
MOZ_ASSERT(mDecoder);
|
MOZ_ASSERT(mDecoder);
|
||||||
|
|
||||||
bool justGotFirstFrame = false;
|
bool justGotFirstFrame = false;
|
||||||
|
bool continueDecoding;
|
||||||
|
|
||||||
{
|
{
|
||||||
MutexAutoLock lock(mFramesMutex);
|
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();
|
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()) {
|
if (!frame || (!mFrames.Frames().IsEmpty() &&
|
||||||
return; // We already have this one.
|
mFrames.Frames().LastElement().get() == frame.get())) {
|
||||||
|
return mFrames.MarkComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append the new frame to the list.
|
// 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;
|
justGotFirstFrame = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -240,6 +374,8 @@ AnimationSurfaceProvider::CheckForNewFrameAtTerminalState()
|
||||||
if (justGotFirstFrame) {
|
if (justGotFirstFrame) {
|
||||||
AnnounceSurfaceAvailable();
|
AnnounceSurfaceAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return continueDecoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -260,14 +396,27 @@ void
|
||||||
AnimationSurfaceProvider::FinishDecoding()
|
AnimationSurfaceProvider::FinishDecoding()
|
||||||
{
|
{
|
||||||
mDecodingMutex.AssertCurrentThreadOwns();
|
mDecodingMutex.AssertCurrentThreadOwns();
|
||||||
MOZ_ASSERT(mImage);
|
|
||||||
MOZ_ASSERT(mDecoder);
|
MOZ_ASSERT(mDecoder);
|
||||||
|
|
||||||
|
if (mImage) {
|
||||||
// Send notifications.
|
// Send notifications.
|
||||||
NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
|
NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
|
||||||
|
}
|
||||||
|
|
||||||
// Destroy our decoder; we don't need it anymore.
|
// 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;
|
mDecoder = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// We don't need a reference to our image anymore, either, and we don't want
|
// 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
|
// one. We may be stored in the surface cache for a long time after decoding
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include "FrameAnimator.h"
|
#include "FrameAnimator.h"
|
||||||
#include "IDecodingTask.h"
|
#include "IDecodingTask.h"
|
||||||
#include "ISurfaceProvider.h"
|
#include "ISurfaceProvider.h"
|
||||||
|
#include "AnimationFrameBuffer.h"
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace image {
|
namespace image {
|
||||||
|
|
@ -31,7 +32,8 @@ public:
|
||||||
|
|
||||||
AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
|
AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
|
||||||
const SurfaceKey& aSurfaceKey,
|
const SurfaceKey& aSurfaceKey,
|
||||||
NotNull<Decoder*> aDecoder);
|
NotNull<Decoder*> aDecoder,
|
||||||
|
size_t aCurrentFrame);
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
@ -44,10 +46,13 @@ public:
|
||||||
DrawableSurface Surface() override { return DrawableSurface(WrapNotNull(this)); }
|
DrawableSurface Surface() override { return DrawableSurface(WrapNotNull(this)); }
|
||||||
|
|
||||||
bool IsFinished() const override;
|
bool IsFinished() const override;
|
||||||
|
bool IsFullyDecoded() const override;
|
||||||
size_t LogicalSizeInBytes() const override;
|
size_t LogicalSizeInBytes() const override;
|
||||||
void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
|
void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
|
||||||
size_t& aHeapSizeOut,
|
size_t& aHeapSizeOut,
|
||||||
size_t& aNonHeapSizeOut) override;
|
size_t& aNonHeapSizeOut) override;
|
||||||
|
void Reset() override;
|
||||||
|
void Advance(size_t aFrame) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DrawableFrameRef DrawableRef(size_t aFrame) override;
|
DrawableFrameRef DrawableRef(size_t aFrame) override;
|
||||||
|
|
@ -77,11 +82,15 @@ private:
|
||||||
virtual ~AnimationSurfaceProvider();
|
virtual ~AnimationSurfaceProvider();
|
||||||
|
|
||||||
void DropImageReference();
|
void DropImageReference();
|
||||||
void CheckForNewFrameAtYield();
|
|
||||||
void CheckForNewFrameAtTerminalState();
|
|
||||||
void AnnounceSurfaceAvailable();
|
void AnnounceSurfaceAvailable();
|
||||||
void FinishDecoding();
|
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.
|
/// The image associated with our decoder.
|
||||||
RefPtr<RasterImage> mImage;
|
RefPtr<RasterImage> mImage;
|
||||||
|
|
||||||
|
|
@ -95,7 +104,7 @@ private:
|
||||||
mutable Mutex mFramesMutex;
|
mutable Mutex mFramesMutex;
|
||||||
|
|
||||||
/// The frames of this animation, in order.
|
/// The frames of this animation, in order.
|
||||||
nsTArray<RawAccessFrameRef> mFrames;
|
AnimationFrameBuffer mFrames;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace image
|
} // namespace image
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,12 @@ public:
|
||||||
mMonitor.NotifyAll();
|
mMonitor.NotifyAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsShuttingDown() const
|
||||||
|
{
|
||||||
|
MonitorAutoLock lock(mMonitor);
|
||||||
|
return mShuttingDown;
|
||||||
|
}
|
||||||
|
|
||||||
/// Pushes a new decode work item.
|
/// Pushes a new decode work item.
|
||||||
void PushWork(IDecodingTask* aTask)
|
void PushWork(IDecodingTask* aTask)
|
||||||
{
|
{
|
||||||
|
|
@ -150,7 +156,7 @@ private:
|
||||||
nsThreadPoolNaming mThreadNaming;
|
nsThreadPoolNaming mThreadNaming;
|
||||||
|
|
||||||
// mMonitor guards the queues and mShuttingDown.
|
// mMonitor guards the queues and mShuttingDown.
|
||||||
Monitor mMonitor;
|
mutable Monitor mMonitor;
|
||||||
nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue;
|
nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue;
|
||||||
nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue;
|
nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue;
|
||||||
bool mShuttingDown;
|
bool mShuttingDown;
|
||||||
|
|
@ -299,6 +305,12 @@ DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*)
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
DecodePool::IsShuttingDown() const
|
||||||
|
{
|
||||||
|
return mImpl->IsShuttingDown();
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
DecodePool::AsyncRun(IDecodingTask* aTask)
|
DecodePool::AsyncRun(IDecodingTask* aTask)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,10 @@ public:
|
||||||
/// same as the number of decoding threads we're actually using.
|
/// same as the number of decoding threads we're actually using.
|
||||||
static uint32_t NumberOfCores();
|
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.
|
/// Ask the DecodePool to run @aTask asynchronously and return immediately.
|
||||||
void AsyncRun(IDecodingTask* aTask);
|
void AsyncRun(IDecodingTask* aTask);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ Decoder::Decoder(RasterImage* aImage)
|
||||||
, mHaveExplicitOutputSize(false)
|
, mHaveExplicitOutputSize(false)
|
||||||
, mInFrame(false)
|
, mInFrame(false)
|
||||||
, mFinishedNewFrame(false)
|
, mFinishedNewFrame(false)
|
||||||
|
, mHasFrameToTake(false)
|
||||||
, mReachedTerminalState(false)
|
, mReachedTerminalState(false)
|
||||||
, mDecodeDone(false)
|
, mDecodeDone(false)
|
||||||
, mError(false)
|
, mError(false)
|
||||||
|
|
@ -254,6 +255,8 @@ Decoder::AllocateFrame(const gfx::IntSize& aOutputSize,
|
||||||
mCurrentFrame.get());
|
mCurrentFrame.get());
|
||||||
|
|
||||||
if (mCurrentFrame) {
|
if (mCurrentFrame) {
|
||||||
|
mHasFrameToTake = true;
|
||||||
|
|
||||||
// Gather the raw pointers the decoders will use.
|
// Gather the raw pointers the decoders will use.
|
||||||
mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
|
mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
|
||||||
mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
|
mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
|
||||||
|
|
@ -474,6 +477,7 @@ Decoder::PostError()
|
||||||
mCurrentFrame->Abort();
|
mCurrentFrame->Abort();
|
||||||
mInFrame = false;
|
mInFrame = false;
|
||||||
--mFrameCount;
|
--mFrameCount;
|
||||||
|
mHasFrameToTake = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,11 @@ public:
|
||||||
mIterator.emplace(Move(aIterator));
|
mIterator.emplace(Move(aIterator));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SourceBuffer* GetSourceBuffer() const
|
||||||
|
{
|
||||||
|
return mIterator->Owner();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should this decoder send partial invalidations?
|
* 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.
|
/// Are we in the middle of a frame right now? Used for assertions only.
|
||||||
bool InFrame() const { return mInFrame; }
|
bool InFrame() const { return mInFrame; }
|
||||||
|
|
||||||
|
/// Type of decoder.
|
||||||
|
virtual DecoderType GetType() const
|
||||||
|
{
|
||||||
|
return DecoderType::UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
enum DecodeStyle {
|
enum DecodeStyle {
|
||||||
PROGRESSIVE, // produce intermediate frames representing the partial
|
PROGRESSIVE, // produce intermediate frames representing the partial
|
||||||
// state of the image
|
// state of the image
|
||||||
|
|
@ -339,6 +350,11 @@ public:
|
||||||
: RawAccessFrameRef();
|
: RawAccessFrameRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HasFrameToTake() const { return mHasFrameToTake; }
|
||||||
|
void ClearHasFrameToTake() {
|
||||||
|
MOZ_ASSERT(mHasFrameToTake);
|
||||||
|
mHasFrameToTake = false;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class nsICODecoder;
|
friend class nsICODecoder;
|
||||||
|
|
@ -493,6 +509,10 @@ private:
|
||||||
bool mInFrame : 1;
|
bool mInFrame : 1;
|
||||||
bool mFinishedNewFrame : 1; // True if PostFrameStop() has been called since
|
bool mFinishedNewFrame : 1; // True if PostFrameStop() has been called since
|
||||||
// the last call to TakeCompleteFrameCount().
|
// 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 mReachedTerminalState : 1;
|
||||||
bool mDecodeDone : 1;
|
bool mDecodeDone : 1;
|
||||||
bool mError : 1;
|
bool mError : 1;
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,8 @@ DecoderFactory::CreateAnimationDecoder(DecoderType aType,
|
||||||
NotNull<SourceBuffer*> aSourceBuffer,
|
NotNull<SourceBuffer*> aSourceBuffer,
|
||||||
const IntSize& aIntrinsicSize,
|
const IntSize& aIntrinsicSize,
|
||||||
DecoderFlags aDecoderFlags,
|
DecoderFlags aDecoderFlags,
|
||||||
SurfaceFlags aSurfaceFlags)
|
SurfaceFlags aSurfaceFlags,
|
||||||
|
size_t aCurrentFrame)
|
||||||
{
|
{
|
||||||
if (aType == DecoderType::UNKNOWN) {
|
if (aType == DecoderType::UNKNOWN) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
@ -213,7 +214,8 @@ DecoderFactory::CreateAnimationDecoder(DecoderType aType,
|
||||||
NotNull<RefPtr<AnimationSurfaceProvider>> provider =
|
NotNull<RefPtr<AnimationSurfaceProvider>> provider =
|
||||||
WrapNotNull(new AnimationSurfaceProvider(aImage,
|
WrapNotNull(new AnimationSurfaceProvider(aImage,
|
||||||
surfaceKey,
|
surfaceKey,
|
||||||
WrapNotNull(decoder)));
|
WrapNotNull(decoder),
|
||||||
|
aCurrentFrame));
|
||||||
|
|
||||||
// Attempt to insert the surface provider into the surface cache right away so
|
// Attempt to insert the surface provider into the surface cache right away so
|
||||||
// we won't trigger any more decoders with the same parameters.
|
// we won't trigger any more decoders with the same parameters.
|
||||||
|
|
@ -226,6 +228,29 @@ DecoderFactory::CreateAnimationDecoder(DecoderType aType,
|
||||||
return task.forget();
|
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>
|
/* static */ already_AddRefed<IDecodingTask>
|
||||||
DecoderFactory::CreateMetadataDecoder(DecoderType aType,
|
DecoderFactory::CreateMetadataDecoder(DecoderType aType,
|
||||||
NotNull<RasterImage*> aImage,
|
NotNull<RasterImage*> aImage,
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ public:
|
||||||
* @param aDecoderFlags Flags specifying the behavior of this decoder.
|
* @param aDecoderFlags Flags specifying the behavior of this decoder.
|
||||||
* @param aSurfaceFlags Flags specifying the type of output this decoder
|
* @param aSurfaceFlags Flags specifying the type of output this decoder
|
||||||
* should produce.
|
* should produce.
|
||||||
|
* @param aCurrentFrame The current frame the decoder should auto advance to.
|
||||||
*/
|
*/
|
||||||
static already_AddRefed<IDecodingTask>
|
static already_AddRefed<IDecodingTask>
|
||||||
CreateAnimationDecoder(DecoderType aType,
|
CreateAnimationDecoder(DecoderType aType,
|
||||||
|
|
@ -99,7 +100,17 @@ public:
|
||||||
NotNull<SourceBuffer*> aSourceBuffer,
|
NotNull<SourceBuffer*> aSourceBuffer,
|
||||||
const gfx::IntSize& aIntrinsicSize,
|
const gfx::IntSize& aIntrinsicSize,
|
||||||
DecoderFlags aDecoderFlags,
|
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
|
* Creates and initializes a metadata decoder of type @aType. This decoder
|
||||||
|
|
|
||||||
|
|
@ -64,12 +64,7 @@ AnimationState::UpdateStateInternal(LookupResult& aResult,
|
||||||
if (mHasBeenDecoded) {
|
if (mHasBeenDecoded) {
|
||||||
Maybe<uint32_t> frameCount = FrameCount();
|
Maybe<uint32_t> frameCount = FrameCount();
|
||||||
MOZ_ASSERT(frameCount.isSome());
|
MOZ_ASSERT(frameCount.isSome());
|
||||||
aResult.Surface().Seek(*frameCount - 1);
|
mIsCurrentlyDecoded = aResult.Surface().IsFullyDecoded();
|
||||||
if (aResult.Surface() && aResult.Surface()->IsFinished()) {
|
|
||||||
mIsCurrentlyDecoded = true;
|
|
||||||
} else {
|
|
||||||
mIsCurrentlyDecoded = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,8 +281,12 @@ FrameAnimator::AdvanceFrame(AnimationState& aState,
|
||||||
// failure) we would have discarded all the old frames and may not yet have
|
// failure) we would have discarded all the old frames and may not yet have
|
||||||
// the new ones.
|
// the new ones.
|
||||||
if (!nextFrame || !nextFrame->IsFinished()) {
|
if (!nextFrame || !nextFrame->IsFinished()) {
|
||||||
// Uh oh, the frame we want to show is currently being decoded (partial)
|
// Uh oh, the frame we want to show is currently being decoded (partial).
|
||||||
// Wait until the next refresh driver tick and try again
|
// 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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,6 +312,7 @@ FrameAnimator::AdvanceFrame(AnimationState& aState,
|
||||||
MOZ_ASSERT(currentFrameEndTime.isSome());
|
MOZ_ASSERT(currentFrameEndTime.isSome());
|
||||||
aState.mCurrentAnimationFrameTime = *currentFrameEndTime;
|
aState.mCurrentAnimationFrameTime = *currentFrameEndTime;
|
||||||
aState.mCurrentAnimationFrameIndex = nextFrameIndex;
|
aState.mCurrentAnimationFrameIndex = nextFrameIndex;
|
||||||
|
aFrames.Advance(nextFrameIndex);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
@ -343,6 +343,7 @@ FrameAnimator::AdvanceFrame(AnimationState& aState,
|
||||||
|
|
||||||
// Set currentAnimationFrameIndex at the last possible moment
|
// Set currentAnimationFrameIndex at the last possible moment
|
||||||
aState.mCurrentAnimationFrameIndex = nextFrameIndex;
|
aState.mCurrentAnimationFrameIndex = nextFrameIndex;
|
||||||
|
aFrames.Advance(nextFrameIndex);
|
||||||
|
|
||||||
// If we're here, we successfully advanced the frame.
|
// If we're here, we successfully advanced the frame.
|
||||||
ret.mFrameAdvanced = true;
|
ret.mFrameAdvanced = true;
|
||||||
|
|
@ -350,6 +351,25 @@ FrameAnimator::AdvanceFrame(AnimationState& aState,
|
||||||
return ret;
|
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
|
RefreshResult
|
||||||
FrameAnimator::RequestRefresh(AnimationState& aState,
|
FrameAnimator::RequestRefresh(AnimationState& aState,
|
||||||
const TimeStamp& aTime,
|
const TimeStamp& aTime,
|
||||||
|
|
|
||||||
|
|
@ -276,6 +276,12 @@ public:
|
||||||
MOZ_COUNT_DTOR(FrameAnimator);
|
MOZ_COUNT_DTOR(FrameAnimator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* Re-evaluate what frame we're supposed to be on, and do whatever blending
|
||||||
* is necessary to get us to that frame.
|
* is necessary to get us to that frame.
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,12 @@ public:
|
||||||
/// @return true if DrawableRef() will return a completely decoded surface.
|
/// @return true if DrawableRef() will return a completely decoded surface.
|
||||||
virtual bool IsFinished() const = 0;
|
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
|
/// @return the number of bytes of memory this ISurfaceProvider is expected to
|
||||||
/// require. Optimizations may result in lower real memory usage. Trivial
|
/// require. Optimizations may result in lower real memory usage. Trivial
|
||||||
/// overhead is ignored. Because this value is used in bookkeeping, it's
|
/// overhead is ignored. Because this value is used in bookkeeping, it's
|
||||||
|
|
@ -75,6 +81,9 @@ public:
|
||||||
ref->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut);
|
ref->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void Reset() { }
|
||||||
|
virtual void Advance(size_t aFrame) { }
|
||||||
|
|
||||||
/// @return the availability state of this ISurfaceProvider, which indicates
|
/// @return the availability state of this ISurfaceProvider, which indicates
|
||||||
/// whether DrawableRef() could successfully return a surface. Should only be
|
/// whether DrawableRef() could successfully return a surface. Should only be
|
||||||
/// called from SurfaceCache code as it relies on SurfaceCache for
|
/// called from SurfaceCache code as it relies on SurfaceCache for
|
||||||
|
|
@ -189,6 +198,36 @@ public:
|
||||||
return mDrawableRef ? NS_OK : NS_ERROR_FAILURE;
|
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; }
|
explicit operator bool() const { return mHaveSurface; }
|
||||||
imgFrame* operator->() { return DrawableRef().get(); }
|
imgFrame* operator->() { return DrawableRef().get(); }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -853,7 +853,8 @@ RasterImage::ResetAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
MOZ_ASSERT(mAnimationState, "Should have AnimationState");
|
MOZ_ASSERT(mAnimationState, "Should have AnimationState");
|
||||||
mAnimationState->ResetAnimation();
|
MOZ_ASSERT(mFrameAnimator, "Should have FrameAnimator");
|
||||||
|
mFrameAnimator->ResetAnimation(*mAnimationState);
|
||||||
|
|
||||||
NotifyProgress(NoProgress, mAnimationState->FirstFrameRefreshArea());
|
NotifyProgress(NoProgress, mAnimationState->FirstFrameRefreshArea());
|
||||||
|
|
||||||
|
|
@ -1183,10 +1184,13 @@ RasterImage::Decode(const IntSize& aSize,
|
||||||
|
|
||||||
// Create a decoder.
|
// Create a decoder.
|
||||||
RefPtr<IDecodingTask> task;
|
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),
|
task = DecoderFactory::CreateAnimationDecoder(mDecoderType, WrapNotNull(this),
|
||||||
mSourceBuffer, mSize,
|
mSourceBuffer, mSize,
|
||||||
decoderFlags, surfaceFlags);
|
decoderFlags, surfaceFlags,
|
||||||
|
currentFrame);
|
||||||
mAnimationState->UpdateState(mAnimationFinished, this, mSize);
|
mAnimationState->UpdateState(mAnimationFinished, this, mSize);
|
||||||
// If the animation is finished we can draw right away because we just draw
|
// 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
|
// the final frame all the time from now on. See comment in
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,12 @@ public:
|
||||||
/// @return a count of the bytes in all chunks we've advanced through.
|
/// @return a count of the bytes in all chunks we've advanced through.
|
||||||
size_t ByteCount() const { return mByteCount; }
|
size_t ByteCount() const { return mByteCount; }
|
||||||
|
|
||||||
|
/// @return the source buffer which owns the iterator.
|
||||||
|
SourceBuffer* Owner() const {
|
||||||
|
MOZ_ASSERT(mOwner);
|
||||||
|
return mOwner;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class SourceBuffer;
|
friend class SourceBuffer;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,8 @@ class nsBMPDecoder : public Decoder
|
||||||
public:
|
public:
|
||||||
~nsBMPDecoder();
|
~nsBMPDecoder();
|
||||||
|
|
||||||
|
DecoderType GetType() const override { return DecoderType::BMP; }
|
||||||
|
|
||||||
/// Obtains the internal output image buffer.
|
/// Obtains the internal output image buffer.
|
||||||
uint32_t* GetImageData() { return reinterpret_cast<uint32_t*>(mImageData); }
|
uint32_t* GetImageData() { return reinterpret_cast<uint32_t*>(mImageData); }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ class nsGIFDecoder2 : public Decoder
|
||||||
public:
|
public:
|
||||||
~nsGIFDecoder2();
|
~nsGIFDecoder2();
|
||||||
|
|
||||||
|
DecoderType GetType() const override { return DecoderType::GIF; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
LexerResult DoDecode(SourceBufferIterator& aIterator,
|
LexerResult DoDecode(SourceBufferIterator& aIterator,
|
||||||
IResumable* aOnResume) override;
|
IResumable* aOnResume) override;
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ public:
|
||||||
/// @return The offset from the beginning of the ICO to the first resource.
|
/// @return The offset from the beginning of the ICO to the first resource.
|
||||||
size_t FirstResourceOffset() const;
|
size_t FirstResourceOffset() const;
|
||||||
|
|
||||||
|
DecoderType GetType() const override { return DecoderType::ICO; }
|
||||||
LexerResult DoDecode(SourceBufferIterator& aIterator,
|
LexerResult DoDecode(SourceBufferIterator& aIterator,
|
||||||
IResumable* aOnResume) override;
|
IResumable* aOnResume) override;
|
||||||
nsresult FinishInternal() override;
|
nsresult FinishInternal() override;
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ class nsIconDecoder : public Decoder
|
||||||
public:
|
public:
|
||||||
virtual ~nsIconDecoder();
|
virtual ~nsIconDecoder();
|
||||||
|
|
||||||
|
DecoderType GetType() const override { return DecoderType::ICON; }
|
||||||
|
|
||||||
LexerResult DoDecode(SourceBufferIterator& aIterator,
|
LexerResult DoDecode(SourceBufferIterator& aIterator,
|
||||||
IResumable* aOnResume) override;
|
IResumable* aOnResume) override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ class nsJPEGDecoder : public Decoder
|
||||||
public:
|
public:
|
||||||
virtual ~nsJPEGDecoder();
|
virtual ~nsJPEGDecoder();
|
||||||
|
|
||||||
|
DecoderType GetType() const override { return DecoderType::JPEG; }
|
||||||
|
|
||||||
virtual void SetSampleSize(int aSampleSize) override
|
virtual void SetSampleSize(int aSampleSize) override
|
||||||
{
|
{
|
||||||
mSampleSize = aSampleSize;
|
mSampleSize = aSampleSize;
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ public:
|
||||||
/// @return true if this PNG is a valid ICO resource.
|
/// @return true if this PNG is a valid ICO resource.
|
||||||
bool IsValidICO() const;
|
bool IsValidICO() const;
|
||||||
|
|
||||||
|
DecoderType GetType() const override { return DecoderType::PNG; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
nsresult InitInternal() override;
|
nsresult InitInternal() override;
|
||||||
LexerResult DoDecode(SourceBufferIterator& aIterator,
|
LexerResult DoDecode(SourceBufferIterator& aIterator,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ class nsWebPDecoder final : public Decoder
|
||||||
public:
|
public:
|
||||||
virtual ~nsWebPDecoder();
|
virtual ~nsWebPDecoder();
|
||||||
|
|
||||||
|
DecoderType GetType() const override { return DecoderType::WEBP; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
LexerResult DoDecode(SourceBufferIterator& aIterator,
|
LexerResult DoDecode(SourceBufferIterator& aIterator,
|
||||||
IResumable* aOnResume) override;
|
IResumable* aOnResume) override;
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ EXPORTS += [
|
||||||
]
|
]
|
||||||
|
|
||||||
UNIFIED_SOURCES += [
|
UNIFIED_SOURCES += [
|
||||||
|
'AnimationFrameBuffer.cpp',
|
||||||
'AnimationSurfaceProvider.cpp',
|
'AnimationSurfaceProvider.cpp',
|
||||||
'ClippedImage.cpp',
|
'ClippedImage.cpp',
|
||||||
'DecodedSurfaceProvider.cpp',
|
'DecodedSurfaceProvider.cpp',
|
||||||
|
|
|
||||||
|
|
@ -4187,6 +4187,15 @@ pref("toolkit.zoomManager.zoomValues", ".3,.5,.67,.8,.9,1,1.1,1.2,1.33,1.5,1.7,2
|
||||||
// Image-related prefs
|
// Image-related prefs
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// The maximum size (in kB) that the aggregate frames of an animation can use
|
||||||
|
// before it starts to discard already displayed frames and redecode them as
|
||||||
|
// necessary.
|
||||||
|
pref("image.animated.decode-on-demand.threshold-kb", 262144);
|
||||||
|
|
||||||
|
// The minimum number of frames we want to have buffered ahead of an
|
||||||
|
// animation's currently displayed frame.
|
||||||
|
pref("image.animated.decode-on-demand.batch-size", 6);
|
||||||
|
|
||||||
// The maximum size, in bytes, of the decoded images we cache
|
// The maximum size, in bytes, of the decoded images we cache
|
||||||
pref("image.cache.size", 5242880);
|
pref("image.cache.size", 5242880);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue