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.mem.decode_bytes_at_a_time", ImageMemDecodeBytesAtATime, uint32_t, 200000);
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.max_size_kb", ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024);
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
DynamicImage::OnSurfaceDiscarded()
DynamicImage::OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey)
{ }
void

View file

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

View file

@ -11,6 +11,7 @@
#include "LookupResult.h"
#include "MainThreadUtils.h"
#include "RasterImage.h"
#include "gfxPrefs.h"
#include "pixman.h"
@ -25,9 +26,80 @@ namespace image {
///////////////////////////////////////////////////////////////////////////////
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
@ -52,7 +124,7 @@ AnimationState::UpdateKnownFrameCount(uint32_t aFrameCount)
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?");
mFrameCount = aFrameCount;
@ -61,7 +133,7 @@ AnimationState::UpdateKnownFrameCount(uint32_t aFrameCount)
Maybe<uint32_t>
AnimationState::FrameCount() const
{
return mDoneDecoding ? Some(mFrameCount) : Nothing();
return mHasBeenDecoded ? Some(mFrameCount) : Nothing();
}
void
@ -98,7 +170,7 @@ AnimationState::LoopLength() const
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 (mAnimationMode != imgIContainer::kNormalAnimMode) {
@ -113,32 +185,41 @@ AnimationState::LoopLength() const
// FrameAnimator implementation.
///////////////////////////////////////////////////////////////////////////////
TimeStamp
FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState) const
Maybe<TimeStamp>
FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState,
DrawableSurface& aFrames) const
{
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
// 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
// RequestRefresh() below.
// XXX(seth): It'd be preferable to make our logic work correctly with
// infinitely long timeouts.
return TimeStamp::NowLoRes() +
TimeDuration::FromMilliseconds(31536000.0);
return Some(TimeStamp::NowLoRes() +
TimeDuration::FromMilliseconds(31536000.0));
}
TimeDuration durationOfTimeout =
TimeDuration::FromMilliseconds(double(timeout.AsMilliseconds()));
TimeDuration::FromMilliseconds(double(timeout->AsMilliseconds()));
TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
return currentFrameEndTime;
return Some(currentFrameEndTime);
}
RefreshResult
FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
FrameAnimator::AdvanceFrame(AnimationState& aState,
DrawableSurface& aFrames,
TimeStamp aTime)
{
NS_ASSERTION(aTime <= TimeStamp::Now(),
"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
// with AnimationState.
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
// 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;
}
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;
}
@ -220,11 +305,13 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1);
// Change frame
if (!DoBlend(&ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) {
if (!DoBlend(aFrames, &ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) {
// something went wrong, move on to next
NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
nextFrame->SetCompositingFailed(true);
aState.mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(aState);
Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState, aFrames);
MOZ_ASSERT(currentFrameEndTime.isSome());
aState.mCurrentAnimationFrameTime = *currentFrameEndTime;
aState.mCurrentAnimationFrameIndex = nextFrameIndex;
return ret;
@ -233,7 +320,9 @@ FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
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
// 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
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.
RefreshResult ret;
while (currentFrameEndTime <= aTime) {
TimeStamp oldFrameEndTime = currentFrameEndTime;
if (aState.IsDiscarded()) {
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.
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,
// then we need to break out of this loop & wait for the frame(s)
// to finish downloading.
if (!frameRes.mFrameAdvanced && (currentFrameEndTime == oldFrameEndTime)) {
if (!frameRes.mFrameAdvanced && (*currentFrameEndTime == oldFrameEndTime)) {
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;
}
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 (mLastCompositedFrameIndex == int32_t(aFrameNum)) {
if (mLastCompositedFrameIndex >= 0 &&
(uint32_t(mLastCompositedFrameIndex) == aState.mCurrentAnimationFrameIndex)) {
return LookupResult(DrawableSurface(mCompositingFrame->DrawableRef()),
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
// 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);
}
@ -324,17 +460,19 @@ FrameAnimator::GetCompositedFrame(uint32_t aFrameNum)
return result;
}
FrameTimeout
FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
Maybe<FrameTimeout>
FrameAnimator::GetTimeoutForFrame(AnimationState& aState,
DrawableSurface& aFrames,
uint32_t aFrameNum) const
{
RawAccessFrameRef frame = GetRawFrame(aFrameNum);
RawAccessFrameRef frame = GetRawFrame(aFrames, aFrameNum);
if (frame) {
AnimationData data = frame->GetAnimationData();
return data.mTimeout;
return Some(data.mTimeout);
}
NS_WARNING("No frame; called GetTimeoutForFrame too early?");
return FrameTimeout::FromRawMilliseconds(100);
MOZ_ASSERT(aState.mHasBeenDecoded && !aState.mIsCurrentlyDecoded);
return Nothing();
}
static void
@ -382,37 +520,29 @@ FrameAnimator::CollectSizeOfCompositingSurfaces(
}
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
// frame we're looking for, so we bail here to avoid returning the wrong frame
// to the caller.
if (NS_FAILED(result.Surface().Seek(aFrameNum))) {
if (NS_FAILED(aFrames.Seek(aFrameNum))) {
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
// update the composited frame of the animation.
bool
FrameAnimator::DoBlend(IntRect* aDirtyRect,
FrameAnimator::DoBlend(DrawableSurface& aFrames,
IntRect* aDirtyRect,
uint32_t aPrevFrameIndex,
uint32_t aNextFrameIndex)
{
RawAccessFrameRef prevFrame = GetRawFrame(aPrevFrameIndex);
RawAccessFrameRef nextFrame = GetRawFrame(aNextFrameIndex);
RawAccessFrameRef prevFrame = GetRawFrame(aFrames, aPrevFrameIndex);
RawAccessFrameRef nextFrame = GetRawFrame(aFrames, aNextFrameIndex);
MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here");

View file

@ -15,11 +15,13 @@
#include "nsCOMPtr.h"
#include "nsRect.h"
#include "SurfaceCache.h"
#include "gfxPrefs.h"
namespace mozilla {
namespace image {
class RasterImage;
class DrawableSurface;
class AnimationState
{
@ -31,14 +33,62 @@ public:
, mLoopCount(-1)
, mFirstFrameTimeout(FrameTimeout::FromRawMilliseconds(0))
, 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
* more frames coming.
* Call this whenever a decode completes, a decode starts, or the image is
* 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
@ -140,8 +190,44 @@ private:
//! The animation mode of this image. Constants defined in imgIContainer.
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
* 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,
* returns an empty LookupResult. It is an error to call this method with
* aFrameNum == 0, because the first frame is never composited.
* Get the full frame for the current frame of the animation (it may or may
* not have required compositing). It may not be available because it hasn't
* 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
@ -227,24 +315,32 @@ private: // methods
* @returns a RefreshResult that shows whether the frame was successfully
* 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.
*/
RawAccessFrameRef GetRawFrame(uint32_t aFrameNum) const;
RawAccessFrameRef GetRawFrame(DrawableSurface& aFrames,
uint32_t aFrameNum) const;
/// @return the given frame's timeout.
FrameTimeout GetTimeoutForFrame(uint32_t aFrameNum) const;
/// @return the given frame's timeout if it is available
Maybe<FrameTimeout> GetTimeoutForFrame(AnimationState& aState,
DrawableSurface& aFrames,
uint32_t aFrameNum) const;
/**
* 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 aNextFrameIndex);

View file

@ -211,7 +211,7 @@ public:
/**
* 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 uint64_t InnerWindowID() const = 0;
@ -249,7 +249,7 @@ public:
}
#endif
virtual void OnSurfaceDiscarded() override { }
virtual void OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) override { }
virtual void SetInnerWindowID(uint64_t aInnerWindowId) override
{

View file

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

View file

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

View file

@ -174,7 +174,7 @@ RasterImage::RequestRefresh(const TimeStamp& aTime)
RefreshResult res;
if (mAnimationState) {
MOZ_ASSERT(mFrameAnimator);
res = mFrameAnimator->RequestRefresh(*mAnimationState, aTime);
res = mFrameAnimator->RequestRefresh(*mAnimationState, aTime, mAnimationFinished);
}
if (res.mFrameAdvanced) {
@ -275,8 +275,7 @@ RasterImage::LookupFrameInternal(const IntSize& aSize,
MOZ_ASSERT(mFrameAnimator);
MOZ_ASSERT(ToSurfaceFlags(aFlags) == DefaultSurfaceFlags(),
"Can't composite frames with non-default surface flags");
const size_t index = mAnimationState->GetCurrentAnimationFrameIndex();
return mFrameAnimator->GetCompositedFrame(index);
return mFrameAnimator->GetCompositedFrame(*mAnimationState);
}
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
// yet.) Trigger decoding so it'll be available next time.
MOZ_ASSERT(aPlaybackType != PlaybackType::eAnimated ||
gfxPrefs::ImageMemAnimatedDiscardable() ||
!mAnimationState || mAnimationState->KnownFrameCount() < 1,
"Animated frames should be locked");
@ -399,7 +399,7 @@ RasterImage::WillDrawOpaqueNow()
return false;
}
if (mAnimationState) {
if (mAnimationState && !gfxPrefs::ImageMemAnimatedDiscardable()) {
// We never discard frames of animated images.
return true;
}
@ -426,11 +426,33 @@ RasterImage::WillDrawOpaqueNow()
}
void
RasterImage::OnSurfaceDiscarded()
RasterImage::OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey)
{
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);
mFrameAnimator = MakeUnique<FrameAnimator>(this, mSize);
// We don't support discarding animated images (See bug 414259).
// Lock the image and throw away the key.
LockImage();
if (!gfxPrefs::ImageMemAnimatedDiscardable()) {
// We don't support discarding animated images (See bug 414259).
// Lock the image and throw away the key.
LockImage();
}
if (!aFromMetadataDecode) {
// The metadata decode reported that this image isn't animated, but we
@ -1015,11 +1039,16 @@ RasterImage::Discard()
{
MOZ_ASSERT(NS_IsMainThread());
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.
SurfaceCache::RemoveImage(ImageKey(this));
if (mAnimationState) {
mAnimationState->UpdateState(mAnimationFinished, this, mSize);
}
// Notify that we discarded.
if (mProgressTracker) {
mProgressTracker->OnDiscard();
@ -1028,8 +1057,8 @@ RasterImage::Discard()
bool
RasterImage::CanDiscard() {
return mHasSourceData && // ...have the source data...
!mAnimationState; // Can never discard animated images
return mHasSourceData && // ...have the source data...
(!mAnimationState || gfxPrefs::ImageMemAnimatedDiscardable()); // Can discard animated images if the pref is set
}
NS_IMETHODIMP
@ -1158,6 +1187,13 @@ RasterImage::Decode(const IntSize& aSize,
task = DecoderFactory::CreateAnimationDecoder(mDecoderType, WrapNotNull(this),
mSourceBuffer, mSize,
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 {
task = DecoderFactory::CreateDecoder(mDecoderType, WrapNotNull(this),
mSourceBuffer, mSize, aSize,
@ -1597,7 +1633,8 @@ RasterImage::NotifyDecodeComplete(const DecoderFinalStatus& aStatus,
mHasBeenDecoded && mAnimationState) {
// 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.
mAnimationState->SetDoneDecoding(true);
mAnimationState->NotifyDecodeComplete();
mAnimationState->UpdateState(mAnimationFinished, this, mSize);
}
// Only act on errors if we have no usable frames from the decoder.

View file

@ -163,7 +163,7 @@ public:
virtual nsresult StopAnimation() override;
// Methods inherited from Image
virtual void OnSurfaceDiscarded() override;
virtual void OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) override;
virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf)
const override;
@ -321,7 +321,9 @@ private:
// 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.
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 OnSurfaceDiscardedInternal(bool aAnimatedFramesDiscarded);
private: // data
nsIntSize mSize;
Orientation mOrientation;

View file

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

View file

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

View file

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

View file

@ -4223,6 +4223,10 @@ pref("image.layerize.always", false);
// compressed data.
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.
pref("image.mem.allow_locking_in_content_processes", true);