Issue #2073 - m-c 1343341: Infrastructure necessary to allow discarding of animated images (squashed)

Includes squashed changes of:
 - m-c 1317907: Refactor FrameAnimator::GetCompositedFrame to be a bit simpler
 - m-c 1351434: bugfix
 - m-c 686905: Enable the pref image.mem.animated.discardable to allow discarding of animated images
This commit is contained in:
Martok 2022-12-30 23:47:18 +01:00 • committed by roytam1
commit eac8afce35
14 changed files with 368 additions and 96 deletions

View file

@ -437,6 +437,7 @@ private:
DECL_GFX_PREF(Once, "image.layerize.always", ImageLayerizeAlways, bool, false); DECL_GFX_PREF(Once, "image.layerize.always", ImageLayerizeAlways, bool, false);
DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time", ImageMemDecodeBytesAtATime, uint32_t, 200000); DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time", ImageMemDecodeBytesAtATime, uint32_t, 200000);
DECL_GFX_PREF(Live, "image.mem.discardable", ImageMemDiscardable, bool, false); DECL_GFX_PREF(Live, "image.mem.discardable", ImageMemDiscardable, bool, false);
DECL_GFX_PREF(Once, "image.mem.animated.discardable", ImageMemAnimatedDiscardable, bool, false);
DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1); DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1);
DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb", ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024); DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb", ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024);
DECL_GFX_PREF(Once, "image.mem.surfacecache.min_expiration_ms", ImageMemSurfaceCacheMinExpirationMS, uint32_t, 60*1000); DECL_GFX_PREF(Once, "image.mem.surfacecache.min_expiration_ms", ImageMemSurfaceCacheMinExpirationMS, uint32_t, 60*1000);

View file

@ -80,7 +80,7 @@ DynamicImage::OnImageDataComplete(nsIRequest* aRequest,
} }
void void
DynamicImage::OnSurfaceDiscarded() DynamicImage::OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey)
{ } { }
void void

View file

@ -53,7 +53,7 @@ public:
nsresult aStatus, nsresult aStatus,
bool aLastPart) override; bool aLastPart) override;
virtual void OnSurfaceDiscarded() override; virtual void OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) override;
virtual void SetInnerWindowID(uint64_t aInnerWindowId) override; virtual void SetInnerWindowID(uint64_t aInnerWindowId) override;
virtual uint64_t InnerWindowID() const override; virtual uint64_t InnerWindowID() const override;

View file

@ -11,6 +11,7 @@
#include "LookupResult.h" #include "LookupResult.h"
#include "MainThreadUtils.h" #include "MainThreadUtils.h"
#include "RasterImage.h" #include "RasterImage.h"
#include "gfxPrefs.h"
#include "pixman.h" #include "pixman.h"
@ -25,9 +26,80 @@ namespace image {
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
void void
AnimationState::SetDoneDecoding(bool aDone) AnimationState::UpdateState(bool aAnimationFinished,
RasterImage *aImage,
const gfx::IntSize& aSize)
{ {
mDoneDecoding = aDone; LookupResult result =
SurfaceCache::Lookup(ImageKey(aImage),
RasterSurfaceKey(aSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
UpdateStateInternal(result, aAnimationFinished);
}
void
AnimationState::UpdateStateInternal(LookupResult& aResult,
bool aAnimationFinished)
{
// Update mDiscarded and mIsCurrentlyDecoded.
if (aResult.Type() == MatchType::NOT_FOUND) {
// no frames, we've either been discarded, or never been decoded before.
mDiscarded = mHasBeenDecoded;
mIsCurrentlyDecoded = false;
} else if (aResult.Type() == MatchType::PENDING) {
// no frames yet, but a decoder is or will be working on it.
mDiscarded = false;
mIsCurrentlyDecoded = false;
} else {
MOZ_ASSERT(aResult.Type() == MatchType::EXACT);
mDiscarded = false;
// If mHasBeenDecoded is true then we know the true total frame count and
// we can use it to determine if we have all the frames now so we know if
// we are currently fully decoded.
// If mHasBeenDecoded is false then we'll get another UpdateState call
// when the decode finishes.
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;
}
}
}
// Update the value of mCompositedFrameInvalid.
if (mIsCurrentlyDecoded || aAnimationFinished) {
// Animated images that have finished their animation (ie because it is a
// finite length animation) don't have RequestRefresh called on them, and so
// mCompositedFrameInvalid would never get cleared. We clear it here (and
// also in RasterImage::Decode when we create a decoder for an image that
// has finished animated so it can display sooner than waiting until the
// decode completes). We also do it if we are fully decoded. This is safe
// to do for images that aren't finished animating because before we paint
// the refresh driver will call into us to advance to the correct frame,
// and that will succeed because we have all the frames.
mCompositedFrameInvalid = false;
} else if (aResult.Type() == MatchType::NOT_FOUND ||
aResult.Type() == MatchType::PENDING) {
if (mHasBeenDecoded) {
MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
mCompositedFrameInvalid = true;
}
}
// Otherwise don't change the value of mCompositedFrameInvalid, it will be
// updated by RequestRefresh.
}
void
AnimationState::NotifyDecodeComplete()
{
mHasBeenDecoded = true;
} }
void void
@ -52,7 +124,7 @@ AnimationState::UpdateKnownFrameCount(uint32_t aFrameCount)
return; return;
} }
MOZ_ASSERT(!mDoneDecoding, "Adding new frames after decoding is finished?"); MOZ_ASSERT(!mHasBeenDecoded, "Adding new frames after decoding is finished?");
MOZ_ASSERT(aFrameCount <= mFrameCount + 1, "Skipped a frame?"); MOZ_ASSERT(aFrameCount <= mFrameCount + 1, "Skipped a frame?");
mFrameCount = aFrameCount; mFrameCount = aFrameCount;
@ -61,7 +133,7 @@ AnimationState::UpdateKnownFrameCount(uint32_t aFrameCount)
Maybe<uint32_t> Maybe<uint32_t>
AnimationState::FrameCount() const AnimationState::FrameCount() const
{ {
return mDoneDecoding ? Some(mFrameCount) : Nothing(); return mHasBeenDecoded ? Some(mFrameCount) : Nothing();
} }
void void
@ -98,7 +170,7 @@ AnimationState::LoopLength() const
return FrameTimeout::Forever(); return FrameTimeout::Forever();
} }
MOZ_ASSERT(mDoneDecoding, "We know the loop length but decoding isn't done?"); MOZ_ASSERT(mHasBeenDecoded, "We know the loop length but decoding isn't done?");
// If we're not looping, a single loop time has no meaning. // If we're not looping, a single loop time has no meaning.
if (mAnimationMode != imgIContainer::kNormalAnimMode) { if (mAnimationMode != imgIContainer::kNormalAnimMode) {
@ -113,32 +185,41 @@ AnimationState::LoopLength() const
// FrameAnimator implementation. // FrameAnimator implementation.
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
TimeStamp Maybe<TimeStamp>
FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState) const FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState,
DrawableSurface& aFrames) const
{ {
TimeStamp currentFrameTime = aState.mCurrentAnimationFrameTime; TimeStamp currentFrameTime = aState.mCurrentAnimationFrameTime;
FrameTimeout timeout = GetTimeoutForFrame(aState.mCurrentAnimationFrameIndex); Maybe<FrameTimeout> timeout =
GetTimeoutForFrame(aState, aFrames, aState.mCurrentAnimationFrameIndex);
if (timeout == FrameTimeout::Forever()) { if (timeout.isNothing()) {
MOZ_ASSERT(aState.GetHasBeenDecoded() && !aState.GetIsCurrentlyDecoded());
return Nothing();
}
if (*timeout == FrameTimeout::Forever()) {
// We need to return a sentinel value in this case, because our logic // We need to return a sentinel value in this case, because our logic
// doesn't work correctly if we have an infinitely long timeout. We use one // doesn't work correctly if we have an infinitely long timeout. We use one
// year in the future as the sentinel because it works with the loop in // year in the future as the sentinel because it works with the loop in
// RequestRefresh() below. // RequestRefresh() below.
// XXX(seth): It'd be preferable to make our logic work correctly with // XXX(seth): It'd be preferable to make our logic work correctly with
// infinitely long timeouts. // infinitely long timeouts.
return TimeStamp::NowLoRes() + return Some(TimeStamp::NowLoRes() +
TimeDuration::FromMilliseconds(31536000.0); TimeDuration::FromMilliseconds(31536000.0));
} }
TimeDuration durationOfTimeout = TimeDuration durationOfTimeout =
TimeDuration::FromMilliseconds(double(timeout.AsMilliseconds())); TimeDuration::FromMilliseconds(double(timeout->AsMilliseconds()));
TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout; TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
return currentFrameEndTime; return Some(currentFrameEndTime);
} }
RefreshResult RefreshResult
FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime) FrameAnimator::AdvanceFrame(AnimationState& aState,
DrawableSurface& aFrames,
TimeStamp aTime)
{ {
NS_ASSERTION(aTime <= TimeStamp::Now(), NS_ASSERTION(aTime <= TimeStamp::Now(),
"Given time appears to be in the future"); "Given time appears to be in the future");
@ -198,7 +279,7 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
// the appropriate notification on the main thread. Make sure we stay in sync // the appropriate notification on the main thread. Make sure we stay in sync
// with AnimationState. // with AnimationState.
MOZ_ASSERT(nextFrameIndex < aState.KnownFrameCount()); MOZ_ASSERT(nextFrameIndex < aState.KnownFrameCount());
RawAccessFrameRef nextFrame = GetRawFrame(nextFrameIndex); RawAccessFrameRef nextFrame = GetRawFrame(aFrames, nextFrameIndex);
// We should always check to see if we have the next frame even if we have // We should always check to see if we have the next frame even if we have
// previously finished decoding. If we needed to redecode (e.g. due to a draw // previously finished decoding. If we needed to redecode (e.g. due to a draw
@ -210,7 +291,11 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
return ret; return ret;
} }
if (GetTimeoutForFrame(nextFrameIndex) == FrameTimeout::Forever()) { Maybe<FrameTimeout> nextFrameTimeout = GetTimeoutForFrame(aState, aFrames, nextFrameIndex);
// GetTimeoutForFrame can only return none if frame doesn't exist,
// but we just got it above.
MOZ_ASSERT(nextFrameTimeout.isSome());
if (*nextFrameTimeout == FrameTimeout::Forever()) {
ret.mAnimationFinished = true; ret.mAnimationFinished = true;
} }
@ -220,11 +305,13 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1); MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1);
// Change frame // Change frame
if (!DoBlend(&ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) { if (!DoBlend(aFrames, &ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) {
// something went wrong, move on to next // something went wrong, move on to next
NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed"); NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
nextFrame->SetCompositingFailed(true); nextFrame->SetCompositingFailed(true);
aState.mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(aState); Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState, aFrames);
MOZ_ASSERT(currentFrameEndTime.isSome());
aState.mCurrentAnimationFrameTime = *currentFrameEndTime;
aState.mCurrentAnimationFrameIndex = nextFrameIndex; aState.mCurrentAnimationFrameIndex = nextFrameIndex;
return ret; return ret;
@ -233,7 +320,9 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
nextFrame->SetCompositingFailed(false); nextFrame->SetCompositingFailed(false);
} }
aState.mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(aState); Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState, aFrames);
MOZ_ASSERT(currentFrameEndTime.isSome());
aState.mCurrentAnimationFrameTime = *currentFrameEndTime;
// If we can get closer to the current time by a multiple of the image's loop // If we can get closer to the current time by a multiple of the image's loop
// time, we should. We can only do this if we're done decoding; otherwise, we // time, we should. We can only do this if we're done decoding; otherwise, we
@ -262,41 +351,88 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
} }
RefreshResult RefreshResult
FrameAnimator::RequestRefresh(AnimationState& aState, const TimeStamp& aTime) FrameAnimator::RequestRefresh(AnimationState& aState,
const TimeStamp& aTime,
bool aAnimationFinished)
{ {
// only advance the frame if the current time is greater than or
// equal to the current frame's end time.
TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime(aState);
// By default, an empty RefreshResult. // By default, an empty RefreshResult.
RefreshResult ret; RefreshResult ret;
while (currentFrameEndTime <= aTime) { if (aState.IsDiscarded()) {
TimeStamp oldFrameEndTime = currentFrameEndTime; return ret;
}
RefreshResult frameRes = AdvanceFrame(aState, aTime); // Get the animation frames once now, and pass them down to callees because
// the surface could be discarded at anytime on a different thread. This is
// must easier to reason about then trying to write code that is safe to
// having the surface disappear at anytime.
LookupResult result =
SurfaceCache::Lookup(ImageKey(mImage),
RasterSurfaceKey(mSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
aState.UpdateStateInternal(result, aAnimationFinished);
if (aState.IsDiscarded() || !result) {
return ret;
}
// only advance the frame if the current time is greater than or
// equal to the current frame's end time.
Maybe<TimeStamp> currentFrameEndTime =
GetCurrentImgFrameEndTime(aState, result.Surface());
if (currentFrameEndTime.isNothing()) {
MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
MOZ_ASSERT(aState.GetHasBeenDecoded() && !aState.GetIsCurrentlyDecoded());
MOZ_ASSERT(aState.mCompositedFrameInvalid);
// Nothing we can do but wait for our previous current frame to be decoded
// again so we can determine what to do next.
return ret;
}
while (*currentFrameEndTime <= aTime) {
TimeStamp oldFrameEndTime = *currentFrameEndTime;
RefreshResult frameRes = AdvanceFrame(aState, result.Surface(), aTime);
// Accumulate our result for returning to callers. // Accumulate our result for returning to callers.
ret.Accumulate(frameRes); ret.Accumulate(frameRes);
currentFrameEndTime = GetCurrentImgFrameEndTime(aState); currentFrameEndTime = GetCurrentImgFrameEndTime(aState, result.Surface());
// AdvanceFrame can't advance to a frame that doesn't exist yet.
MOZ_ASSERT(currentFrameEndTime.isSome());
// If we didn't advance a frame, and our frame end time didn't change, // If we didn't advance a frame, and our frame end time didn't change,
// then we need to break out of this loop & wait for the frame(s) // then we need to break out of this loop & wait for the frame(s)
// to finish downloading. // to finish downloading.
if (!frameRes.mFrameAdvanced && (currentFrameEndTime == oldFrameEndTime)) { if (!frameRes.mFrameAdvanced && (*currentFrameEndTime == oldFrameEndTime)) {
break; break;
} }
} }
// Advanced to the correct frame, the composited frame is now valid to be drawn.
if (*currentFrameEndTime > aTime) {
aState.mCompositedFrameInvalid = false;
}
MOZ_ASSERT(!aState.mIsCurrentlyDecoded || !aState.mCompositedFrameInvalid);
return ret; return ret;
} }
LookupResult LookupResult
FrameAnimator::GetCompositedFrame(uint32_t aFrameNum) FrameAnimator::GetCompositedFrame(AnimationState& aState)
{ {
if (aState.mCompositedFrameInvalid) {
MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
MOZ_ASSERT(aState.GetHasBeenDecoded());
MOZ_ASSERT(!aState.GetIsCurrentlyDecoded());
return LookupResult(MatchType::NOT_FOUND);
}
// If we have a composited version of this frame, return that. // If we have a composited version of this frame, return that.
if (mLastCompositedFrameIndex == int32_t(aFrameNum)) { if (mLastCompositedFrameIndex >= 0 &&
(uint32_t(mLastCompositedFrameIndex) == aState.mCurrentAnimationFrameIndex)) {
return LookupResult(DrawableSurface(mCompositingFrame->DrawableRef()), return LookupResult(DrawableSurface(mCompositingFrame->DrawableRef()),
MatchType::EXACT); MatchType::EXACT);
} }
@ -314,7 +450,7 @@ FrameAnimator::GetCompositedFrame(uint32_t aFrameNum)
// Seek to the appropriate frame. If seeking fails, it means that we couldn't // Seek to the appropriate frame. If seeking fails, it means that we couldn't
// get the frame we're looking for; treat this as if the lookup failed. // get the frame we're looking for; treat this as if the lookup failed.
if (NS_FAILED(result.Surface().Seek(aFrameNum))) { if (NS_FAILED(result.Surface().Seek(aState.mCurrentAnimationFrameIndex))) {
return LookupResult(MatchType::NOT_FOUND); return LookupResult(MatchType::NOT_FOUND);
} }
@ -324,17 +460,19 @@ FrameAnimator::GetCompositedFrame(uint32_t aFrameNum)
return result; return result;
} }
FrameTimeout Maybe<FrameTimeout>
FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const FrameAnimator::GetTimeoutForFrame(AnimationState& aState,
DrawableSurface& aFrames,
uint32_t aFrameNum) const
{ {
RawAccessFrameRef frame = GetRawFrame(aFrameNum); RawAccessFrameRef frame = GetRawFrame(aFrames, aFrameNum);
if (frame) { if (frame) {
AnimationData data = frame->GetAnimationData(); AnimationData data = frame->GetAnimationData();
return data.mTimeout; return Some(data.mTimeout);
} }
NS_WARNING("No frame; called GetTimeoutForFrame too early?"); MOZ_ASSERT(aState.mHasBeenDecoded && !aState.mIsCurrentlyDecoded);
return FrameTimeout::FromRawMilliseconds(100); return Nothing();
} }
static void static void
@ -382,37 +520,29 @@ FrameAnimator::CollectSizeOfCompositingSurfaces(
} }
RawAccessFrameRef RawAccessFrameRef
FrameAnimator::GetRawFrame(uint32_t aFrameNum) const FrameAnimator::GetRawFrame(DrawableSurface& aFrames, uint32_t aFrameNum) const
{ {
LookupResult result =
SurfaceCache::Lookup(ImageKey(mImage),
RasterSurfaceKey(mSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
if (!result) {
return RawAccessFrameRef();
}
// Seek to the frame we want. If seeking fails, it means we couldn't get the // Seek to the frame we want. If seeking fails, it means we couldn't get the
// frame we're looking for, so we bail here to avoid returning the wrong frame // frame we're looking for, so we bail here to avoid returning the wrong frame
// to the caller. // to the caller.
if (NS_FAILED(result.Surface().Seek(aFrameNum))) { if (NS_FAILED(aFrames.Seek(aFrameNum))) {
return RawAccessFrameRef(); // Not available yet. return RawAccessFrameRef(); // Not available yet.
} }
return result.Surface()->RawAccessRef(); return aFrames->RawAccessRef();
} }
//****************************************************************************** //******************************************************************************
// DoBlend gets called when the timer for animation get fired and we have to // DoBlend gets called when the timer for animation get fired and we have to
// update the composited frame of the animation. // update the composited frame of the animation.
bool bool
FrameAnimator::DoBlend(IntRect* aDirtyRect, FrameAnimator::DoBlend(DrawableSurface& aFrames,
IntRect* aDirtyRect,
uint32_t aPrevFrameIndex, uint32_t aPrevFrameIndex,
uint32_t aNextFrameIndex) uint32_t aNextFrameIndex)
{ {
RawAccessFrameRef prevFrame = GetRawFrame(aPrevFrameIndex); RawAccessFrameRef prevFrame = GetRawFrame(aFrames, aPrevFrameIndex);
RawAccessFrameRef nextFrame = GetRawFrame(aNextFrameIndex); RawAccessFrameRef nextFrame = GetRawFrame(aFrames, aNextFrameIndex);
MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here"); MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here");

View file

@ -15,11 +15,13 @@
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsRect.h" #include "nsRect.h"
#include "SurfaceCache.h" #include "SurfaceCache.h"
#include "gfxPrefs.h"
namespace mozilla { namespace mozilla {
namespace image { namespace image {
class RasterImage; class RasterImage;
class DrawableSurface;
class AnimationState class AnimationState
{ {
@ -31,14 +33,62 @@ public:
, mLoopCount(-1) , mLoopCount(-1)
, mFirstFrameTimeout(FrameTimeout::FromRawMilliseconds(0)) , mFirstFrameTimeout(FrameTimeout::FromRawMilliseconds(0))
, mAnimationMode(aAnimationMode) , mAnimationMode(aAnimationMode)
, mDoneDecoding(false) , mHasBeenDecoded(false)
, mIsCurrentlyDecoded(false)
, mCompositedFrameInvalid(false)
, mDiscarded(false)
{ } { }
/** /**
* Call when this image is finished decoding so we know that there aren't any * Call this whenever a decode completes, a decode starts, or the image is
* more frames coming. * discarded. It will update the internal state. Specifically mDiscarded,
* mCompositedFrameInvalid, and mIsCurrentlyDecoded.
*/ */
void SetDoneDecoding(bool aDone); void UpdateState(bool aAnimationFinished,
RasterImage *aImage,
const gfx::IntSize& aSize);
private:
void UpdateStateInternal(LookupResult& aResult,
bool aAnimationFinished);
public:
/**
* Call when a decode of this image has been completed.
*/
void NotifyDecodeComplete();
/**
* Returns true if this image has been fully decoded before.
*/
bool GetHasBeenDecoded() { return mHasBeenDecoded; }
/**
* Returns true if this image has been discarded and a decoded has not yet
* been created to redecode it.
*/
bool IsDiscarded() { return mDiscarded; }
/**
* Sets the composited frame as valid or invalid.
*/
void SetCompositedFrameInvalid(bool aInvalid) {
MOZ_ASSERT(!aInvalid || gfxPrefs::ImageMemAnimatedDiscardable());
mCompositedFrameInvalid = aInvalid;
}
/**
* Returns whether the composited frame is valid to draw to the screen.
*/
bool GetCompositedFrameInvalid() {
return mCompositedFrameInvalid;
}
/**
* Returns whether the image is currently full decoded..
*/
bool GetIsCurrentlyDecoded() {
return mIsCurrentlyDecoded;
}
/** /**
* Call when you need to re-start animating. Ensures we start from the first * Call when you need to re-start animating. Ensures we start from the first
@ -140,8 +190,44 @@ private:
//! The animation mode of this image. Constants defined in imgIContainer. //! The animation mode of this image. Constants defined in imgIContainer.
uint16_t mAnimationMode; uint16_t mAnimationMode;
//! Whether this image is done being decoded. /**
bool mDoneDecoding; * The following four bools (mHasBeenDecoded, mIsCurrentlyDecoded,
* mCompositedFrameInvalid, mDiscarded) track the state of the image with
* regards to decoding. They all start out false, including mDiscarded,
* because we want to treat being discarded differently from "not yet decoded
* for the first time".
*
* (When we are decoding the image for the first time we want to show the
* image at the speed of data coming in from the network or the speed
* specified in the image file, whichever is slower. But when redecoding we
* want to show nothing until the frame for the current time has been
* decoded. The prevents the user from seeing the image "fast forward"
* to the expected spot.)
*
* When the image is decoded for the first time mHasBeenDecoded and
* mIsCurrentlyDecoded get set to true. When the image is discarded
* mIsCurrentlyDecoded gets set to false, and mCompositedFrameInvalid
* & mDiscarded get set to true. When we create a decoder to redecode the
* image mDiscarded gets set to false. mCompositedFrameInvalid gets set to
* false when we are able to advance to the frame that should be showing
* for the current time. mIsCurrentlyDecoded gets set to true when the
* redecode finishes.
*/
//! Whether this image has been decoded at least once.
bool mHasBeenDecoded;
//! Whether this image is currently fully decoded.
bool mIsCurrentlyDecoded;
//! Whether the composited frame is valid to draw to the screen, note that
//! the composited frame can exist and be filled with image data but not
//! valid to draw to the screen.
bool mCompositedFrameInvalid;
//! Whether this image is currently discarded. Only set to true after the
//! image has been decoded at least once.
bool mDiscarded;
}; };
/** /**
@ -197,14 +283,16 @@ public:
* Returns the result of that blending, including whether the current frame * Returns the result of that blending, including whether the current frame
* changed and what the resulting dirty rectangle is. * changed and what the resulting dirty rectangle is.
*/ */
RefreshResult RequestRefresh(AnimationState& aState, const TimeStamp& aTime); RefreshResult RequestRefresh(AnimationState& aState,
const TimeStamp& aTime,
bool aAnimationFinished);
/** /**
* If we have a composited frame for @aFrameNum, returns it. Otherwise, * Get the full frame for the current frame of the animation (it may or may
* returns an empty LookupResult. It is an error to call this method with * not have required compositing). It may not be available because it hasn't
* aFrameNum == 0, because the first frame is never composited. * been decoded yet, in which case we return an empty LookupResult.
*/ */
LookupResult GetCompositedFrame(uint32_t aFrameNum); LookupResult GetCompositedFrame(AnimationState& aState);
/** /**
* Collect an accounting of the memory occupied by the compositing surfaces we * Collect an accounting of the memory occupied by the compositing surfaces we
@ -227,24 +315,32 @@ private: // methods
* @returns a RefreshResult that shows whether the frame was successfully * @returns a RefreshResult that shows whether the frame was successfully
* advanced, and its resulting dirty rect. * advanced, and its resulting dirty rect.
*/ */
RefreshResult AdvanceFrame(AnimationState& aState, TimeStamp aTime); RefreshResult AdvanceFrame(AnimationState& aState,
DrawableSurface& aFrames,
TimeStamp aTime);
/** /**
* Get the @aIndex-th frame in the frame index, ignoring results of blending. * Get the @aIndex-th frame in the frame index, ignoring results of blending.
*/ */
RawAccessFrameRef GetRawFrame(uint32_t aFrameNum) const; RawAccessFrameRef GetRawFrame(DrawableSurface& aFrames,
uint32_t aFrameNum) const;
/// @return the given frame's timeout. /// @return the given frame's timeout if it is available
FrameTimeout GetTimeoutForFrame(uint32_t aFrameNum) const; Maybe<FrameTimeout> GetTimeoutForFrame(AnimationState& aState,
DrawableSurface& aFrames,
uint32_t aFrameNum) const;
/** /**
* Get the time the frame we're currently displaying is supposed to end. * Get the time the frame we're currently displaying is supposed to end.
* *
* In the error case, returns an "infinity" timestamp. * In the error case (like if the requested frame is not currently
* decoded), returns None().
*/ */
TimeStamp GetCurrentImgFrameEndTime(AnimationState& aState) const; Maybe<TimeStamp> GetCurrentImgFrameEndTime(AnimationState& aState,
DrawableSurface& aFrames) const;
bool DoBlend(gfx::IntRect* aDirtyRect, bool DoBlend(DrawableSurface& aFrames,
gfx::IntRect* aDirtyRect,
uint32_t aPrevFrameIndex, uint32_t aPrevFrameIndex,
uint32_t aNextFrameIndex); uint32_t aNextFrameIndex);

View file

@ -211,7 +211,7 @@ public:
/** /**
* Called when the SurfaceCache discards a surface belonging to this image. * Called when the SurfaceCache discards a surface belonging to this image.
*/ */
virtual void OnSurfaceDiscarded() = 0; virtual void OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) = 0;
virtual void SetInnerWindowID(uint64_t aInnerWindowId) = 0; virtual void SetInnerWindowID(uint64_t aInnerWindowId) = 0;
virtual uint64_t InnerWindowID() const = 0; virtual uint64_t InnerWindowID() const = 0;
@ -249,7 +249,7 @@ public:
} }
#endif #endif
virtual void OnSurfaceDiscarded() override { } virtual void OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) override { }
virtual void SetInnerWindowID(uint64_t aInnerWindowId) override virtual void SetInnerWindowID(uint64_t aInnerWindowId) override
{ {

View file

@ -88,9 +88,9 @@ ImageWrapper::OnImageDataComplete(nsIRequest* aRequest,
} }
void void
ImageWrapper::OnSurfaceDiscarded() ImageWrapper::OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey)
{ {
return mInnerImage->OnSurfaceDiscarded(); return mInnerImage->OnSurfaceDiscarded(aSurfaceKey);
} }
void void

View file

@ -45,7 +45,7 @@ public:
nsresult aStatus, nsresult aStatus,
bool aLastPart) override; bool aLastPart) override;
virtual void OnSurfaceDiscarded() override; virtual void OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) override;
virtual void SetInnerWindowID(uint64_t aInnerWindowId) override; virtual void SetInnerWindowID(uint64_t aInnerWindowId) override;
virtual uint64_t InnerWindowID() const override; virtual uint64_t InnerWindowID() const override;

View file

@ -174,7 +174,7 @@ RasterImage::RequestRefresh(const TimeStamp& aTime)
RefreshResult res; RefreshResult res;
if (mAnimationState) { if (mAnimationState) {
MOZ_ASSERT(mFrameAnimator); MOZ_ASSERT(mFrameAnimator);
res = mFrameAnimator->RequestRefresh(*mAnimationState, aTime); res = mFrameAnimator->RequestRefresh(*mAnimationState, aTime, mAnimationFinished);
} }
if (res.mFrameAdvanced) { if (res.mFrameAdvanced) {
@ -275,8 +275,7 @@ RasterImage::LookupFrameInternal(const IntSize& aSize,
MOZ_ASSERT(mFrameAnimator); MOZ_ASSERT(mFrameAnimator);
MOZ_ASSERT(ToSurfaceFlags(aFlags) == DefaultSurfaceFlags(), MOZ_ASSERT(ToSurfaceFlags(aFlags) == DefaultSurfaceFlags(),
"Can't composite frames with non-default surface flags"); "Can't composite frames with non-default surface flags");
const size_t index = mAnimationState->GetCurrentAnimationFrameIndex(); return mFrameAnimator->GetCompositedFrame(*mAnimationState);
return mFrameAnimator->GetCompositedFrame(index);
} }
SurfaceFlags surfaceFlags = ToSurfaceFlags(aFlags); SurfaceFlags surfaceFlags = ToSurfaceFlags(aFlags);
@ -332,6 +331,7 @@ RasterImage::LookupFrame(const IntSize& aSize,
// one. (Or we're sync decoding and the existing decoder hasn't even started // one. (Or we're sync decoding and the existing decoder hasn't even started
// yet.) Trigger decoding so it'll be available next time. // yet.) Trigger decoding so it'll be available next time.
MOZ_ASSERT(aPlaybackType != PlaybackType::eAnimated || MOZ_ASSERT(aPlaybackType != PlaybackType::eAnimated ||
gfxPrefs::ImageMemAnimatedDiscardable() ||
!mAnimationState || mAnimationState->KnownFrameCount() < 1, !mAnimationState || mAnimationState->KnownFrameCount() < 1,
"Animated frames should be locked"); "Animated frames should be locked");
@ -399,7 +399,7 @@ RasterImage::WillDrawOpaqueNow()
return false; return false;
} }
if (mAnimationState) { if (mAnimationState && !gfxPrefs::ImageMemAnimatedDiscardable()) {
// We never discard frames of animated images. // We never discard frames of animated images.
return true; return true;
} }
@ -426,11 +426,33 @@ RasterImage::WillDrawOpaqueNow()
} }
void void
RasterImage::OnSurfaceDiscarded() RasterImage::OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey)
{ {
MOZ_ASSERT(mProgressTracker); MOZ_ASSERT(mProgressTracker);
NS_DispatchToMainThread(NewRunnableMethod(mProgressTracker, &ProgressTracker::OnDiscard)); bool animatedFramesDiscarded =
mAnimationState && aSurfaceKey.Playback() == PlaybackType::eAnimated;
RefPtr<RasterImage> image = this;
NS_DispatchToMainThread(NS_NewRunnableFunction(
[=]() -> void {
image->OnSurfaceDiscardedInternal(animatedFramesDiscarded);
}));
}
void
RasterImage::OnSurfaceDiscardedInternal(bool aAnimatedFramesDiscarded)
{
MOZ_ASSERT(NS_IsMainThread());
if (aAnimatedFramesDiscarded && mAnimationState) {
MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
mAnimationState->UpdateState(mAnimationFinished, this, mSize);
}
if (mProgressTracker) {
mProgressTracker->OnDiscard();
}
} }
//****************************************************************************** //******************************************************************************
@ -706,9 +728,11 @@ RasterImage::SetMetadata(const ImageMetadata& aMetadata,
mAnimationState.emplace(mAnimationMode); mAnimationState.emplace(mAnimationMode);
mFrameAnimator = MakeUnique<FrameAnimator>(this, mSize); mFrameAnimator = MakeUnique<FrameAnimator>(this, mSize);
// We don't support discarding animated images (See bug 414259). if (!gfxPrefs::ImageMemAnimatedDiscardable()) {
// Lock the image and throw away the key. // We don't support discarding animated images (See bug 414259).
LockImage(); // Lock the image and throw away the key.
LockImage();
}
if (!aFromMetadataDecode) { if (!aFromMetadataDecode) {
// The metadata decode reported that this image isn't animated, but we // The metadata decode reported that this image isn't animated, but we
@ -1015,11 +1039,16 @@ RasterImage::Discard()
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(CanDiscard(), "Asked to discard but can't"); MOZ_ASSERT(CanDiscard(), "Asked to discard but can't");
MOZ_ASSERT(!mAnimationState, "Asked to discard for animated image"); MOZ_ASSERT(!mAnimationState || gfxPrefs::ImageMemAnimatedDiscardable(),
"Asked to discard for animated image");
// Delete all the decoded frames. // Delete all the decoded frames.
SurfaceCache::RemoveImage(ImageKey(this)); SurfaceCache::RemoveImage(ImageKey(this));
if (mAnimationState) {
mAnimationState->UpdateState(mAnimationFinished, this, mSize);
}
// Notify that we discarded. // Notify that we discarded.
if (mProgressTracker) { if (mProgressTracker) {
mProgressTracker->OnDiscard(); mProgressTracker->OnDiscard();
@ -1028,8 +1057,8 @@ RasterImage::Discard()
bool bool
RasterImage::CanDiscard() { RasterImage::CanDiscard() {
return mHasSourceData && // ...have the source data... return mHasSourceData && // ...have the source data...
!mAnimationState; // Can never discard animated images (!mAnimationState || gfxPrefs::ImageMemAnimatedDiscardable()); // Can discard animated images if the pref is set
} }
NS_IMETHODIMP NS_IMETHODIMP
@ -1158,6 +1187,13 @@ RasterImage::Decode(const IntSize& aSize,
task = DecoderFactory::CreateAnimationDecoder(mDecoderType, WrapNotNull(this), task = DecoderFactory::CreateAnimationDecoder(mDecoderType, WrapNotNull(this),
mSourceBuffer, mSize, mSourceBuffer, mSize,
decoderFlags, surfaceFlags); decoderFlags, surfaceFlags);
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
// AnimationState::UpdateState.
if (mAnimationFinished) {
mAnimationState->SetCompositedFrameInvalid(false);
}
} else { } else {
task = DecoderFactory::CreateDecoder(mDecoderType, WrapNotNull(this), task = DecoderFactory::CreateDecoder(mDecoderType, WrapNotNull(this),
mSourceBuffer, mSize, aSize, mSourceBuffer, mSize, aSize,
@ -1597,7 +1633,8 @@ RasterImage::NotifyDecodeComplete(const DecoderFinalStatus& aStatus,
mHasBeenDecoded && mAnimationState) { mHasBeenDecoded && mAnimationState) {
// We've finished a full decode of all animation frames and our AnimationState // We've finished a full decode of all animation frames and our AnimationState
// has been notified about them all, so let it know not to expect anymore. // has been notified about them all, so let it know not to expect anymore.
mAnimationState->SetDoneDecoding(true); mAnimationState->NotifyDecodeComplete();
mAnimationState->UpdateState(mAnimationFinished, this, mSize);
} }
// Only act on errors if we have no usable frames from the decoder. // Only act on errors if we have no usable frames from the decoder.

View file

@ -163,7 +163,7 @@ public:
virtual nsresult StopAnimation() override; virtual nsresult StopAnimation() override;
// Methods inherited from Image // Methods inherited from Image
virtual void OnSurfaceDiscarded() override; virtual void OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) override;
virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf)
const override; const override;
@ -321,7 +321,9 @@ private:
// never unlock so that animated images always have their lock count >= 1. In // never unlock so that animated images always have their lock count >= 1. In
// that case we use our animation consumers count as a proxy for lock count. // that case we use our animation consumers count as a proxy for lock count.
bool IsUnlocked() { bool IsUnlocked() {
return (mLockCount == 0 || (mAnimationState && mAnimationConsumers == 0)); return (mLockCount == 0 ||
(!gfxPrefs::ImageMemAnimatedDiscardable() &&
(mAnimationState && mAnimationConsumers == 0)));
} }
@ -378,6 +380,8 @@ private:
*/ */
void RecoverFromInvalidFrames(const nsIntSize& aSize, uint32_t aFlags); void RecoverFromInvalidFrames(const nsIntSize& aSize, uint32_t aFlags);
void OnSurfaceDiscardedInternal(bool aAnimatedFramesDiscarded);
private: // data private: // data
nsIntSize mSize; nsIntSize mSize;
Orientation mOrientation; Orientation mOrientation;

View file

@ -503,7 +503,7 @@ public:
// If the surface was not a placeholder, tell its image that we discarded it. // If the surface was not a placeholder, tell its image that we discarded it.
if (!aSurface->IsPlaceholder()) { if (!aSurface->IsPlaceholder()) {
static_cast<Image*>(imageKey)->OnSurfaceDiscarded(); static_cast<Image*>(imageKey)->OnSurfaceDiscarded(aSurface->GetSurfaceKey());
} }
StopTracking(aSurface, aAutoLock); StopTracking(aSurface, aAutoLock);

View file

@ -1113,7 +1113,7 @@ VectorImage::RequestDiscard()
} }
void void
VectorImage::OnSurfaceDiscarded() VectorImage::OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey)
{ {
MOZ_ASSERT(mProgressTracker); MOZ_ASSERT(mProgressTracker);

View file

@ -49,7 +49,7 @@ public:
nsresult aResult, nsresult aResult,
bool aLastPart) override; bool aLastPart) override;
virtual void OnSurfaceDiscarded() override; virtual void OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) override;
/** /**
* Callback for SVGRootRenderingObserver. * Callback for SVGRootRenderingObserver.

View file

@ -4223,6 +4223,10 @@ pref("image.layerize.always", false);
// compressed data. // compressed data.
pref("image.mem.discardable", true); pref("image.mem.discardable", true);
// Discards inactive image frames of _animated_ images and re-decodes them on
// demand from compressed data. Has no effect if image.mem.discardable is false.
pref("image.mem.animated.discardable", true);
// Allows image locking of decoded image data in content processes. // Allows image locking of decoded image data in content processes.
pref("image.mem.allow_locking_in_content_processes", true); pref("image.mem.allow_locking_in_content_processes", true);