Bug 1454149 - Do not advance animated images which are not displayed. r=tnikkel

All animated images on a page are currently registered with the refresh
driver and advance with the tick refresh. These animations may not even
be in view, and if they are large and thus cause redecoding, cause a
marked increase in CPU usage for no benefit to the user.

This patch adds an additional flag, mCompositedFrameRequested, to the
AnimationState used by FrameAnimator. It is set to true each time the
current animated image frame is requested via
FrameAnimator::GetCompositedFrame. It is set to false each time the
frame is advanced in FrameAnimator::AdvanceFrame (via
FrameAnimator::RequestRefresh). If it is true when
FrameAnimator::RequestRefresh is called, then it will advance the
animation according to the normal rules. If it is false, then it will
set the current animation time to the current time instead of advancing.

This should not cause the animation to fall behind anymore or skip
frames more than it does today. This is because if
FrameAnimator::GetCompositedFrame is not called, then the internal state
of the animation is advancing ahead of what the user sees. Once it is
called, the new frame is far ahead of the previously displayed frame.
The only difference now is that we will display the previous frame for
slightly longer until the next refresh tick.

Note that if an animated image is layerized (should not happen today) or
otherwise uses an ImageContainer, this optimization fails. While we know
whether or not we have an image container, we do not know if anything is
actively using it.
This commit is contained in:
Andrew Osmond 2018-05-09 08:04:20 -04:00
Родитель 49932142ae
Коммит d19566e09c
4 изменённых файлов: 47 добавлений и 0 удалений

Просмотреть файл

@ -532,6 +532,7 @@ private:
DECL_GFX_PREF(Live, "image.animated.decode-on-demand.threshold-kb", ImageAnimatedDecodeOnDemandThresholdKB, uint32_t, 20480);
DECL_GFX_PREF(Live, "image.animated.decode-on-demand.batch-size", ImageAnimatedDecodeOnDemandBatchSize, uint32_t, 6);
DECL_GFX_PREF(Live, "image.animated.resume-from-last-displayed", ImageAnimatedResumeFromLastDisplayed, bool, false);
DECL_GFX_PREF(Live, "image.cache.factor2.threshold-surfaces", ImageCacheFactor2ThresholdSurfaces, int32_t, -1);
DECL_GFX_PREF(Once, "image.cache.size", ImageCacheSize, int32_t, 5*1024*1024);
DECL_GFX_PREF(Once, "image.cache.timeweight", ImageCacheTimeWeight, int32_t, 500);

Просмотреть файл

@ -168,6 +168,21 @@ AnimationState::SetAnimationFrameTime(const TimeStamp& aTime)
mCurrentAnimationFrameTime = aTime;
}
bool
AnimationState::MaybeAdvanceAnimationFrameTime(const TimeStamp& aTime)
{
if (!gfxPrefs::ImageAnimatedResumeFromLastDisplayed() ||
mCurrentAnimationFrameTime >= aTime) {
return false;
}
// We are configured to stop an animation when it is out of view, and restart
// it from the same point when it comes back into view. The same applies if it
// was discarded while out of view.
mCurrentAnimationFrameTime = aTime;
return true;
}
uint32_t
AnimationState::GetCurrentAnimationFrameIndex() const
{
@ -329,6 +344,7 @@ FrameAnimator::AdvanceFrame(AnimationState& aState,
MOZ_ASSERT(currentFrameEndTime.isSome());
aState.mCurrentAnimationFrameTime = *currentFrameEndTime;
aState.mCurrentAnimationFrameIndex = nextFrameIndex;
aState.mCompositedFrameRequested = false;
aFrames.Advance(nextFrameIndex);
return ret;
@ -375,6 +391,7 @@ FrameAnimator::AdvanceFrame(AnimationState& aState,
// Set currentAnimationFrameIndex at the last possible moment
aState.mCurrentAnimationFrameIndex = nextFrameIndex;
aState.mCompositedFrameRequested = false;
aFrames.Advance(nextFrameIndex);
// If we're here, we successfully advanced the frame.
@ -411,6 +428,7 @@ FrameAnimator::RequestRefresh(AnimationState& aState,
RefreshResult ret;
if (aState.IsDiscarded()) {
aState.MaybeAdvanceAnimationFrameTime(aTime);
return ret;
}
@ -426,6 +444,7 @@ FrameAnimator::RequestRefresh(AnimationState& aState,
ret.mDirtyRect = aState.UpdateStateInternal(result, aAnimationFinished, mSize);
if (aState.IsDiscarded() || !result) {
aState.MaybeAdvanceAnimationFrameTime(aTime);
if (!ret.mDirtyRect.IsEmpty()) {
ret.mFrameAdvanced = true;
}
@ -442,6 +461,15 @@ FrameAnimator::RequestRefresh(AnimationState& aState,
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.
aState.MaybeAdvanceAnimationFrameTime(aTime);
return ret;
}
// If nothing has accessed the composited frame since the last time we
// advanced, then there is no point in continuing to advance the animation.
// This has the effect of freezing the animation while not in view.
if (!aState.mCompositedFrameRequested &&
aState.MaybeAdvanceAnimationFrameTime(aTime)) {
return ret;
}
@ -479,6 +507,8 @@ FrameAnimator::RequestRefresh(AnimationState& aState,
LookupResult
FrameAnimator::GetCompositedFrame(AnimationState& aState)
{
aState.mCompositedFrameRequested = true;
LookupResult result =
SurfaceCache::Lookup(ImageKey(mImage),
RasterSurfaceKey(mSize,

Просмотреть файл

@ -37,6 +37,7 @@ public:
, mHasRequestedDecode(false)
, mIsCurrentlyDecoded(false)
, mCompositedFrameInvalid(false)
, mCompositedFrameRequested(false)
, mDiscarded(false)
{ }
@ -141,6 +142,13 @@ public:
*/
void SetAnimationFrameTime(const TimeStamp& aTime);
/**
* Set the animation frame time to @aTime if we are configured to stop the
* animation when not visible and aTime is later than the current time.
* Returns true if the time was updated, else false.
*/
bool MaybeAdvanceAnimationFrameTime(const TimeStamp& aTime);
/**
* The current frame we're on, from 0 to (numFrames - 1).
*/
@ -238,6 +246,10 @@ private:
//! valid to draw to the screen.
bool mCompositedFrameInvalid;
//! Whether the composited frame was requested from the animator since the
//! last time we advanced the animation.
bool mCompositedFrameRequested;
//! Whether this image is currently discarded. Only set to true after the
//! image has been decoded at least once.
bool mDiscarded;

Просмотреть файл

@ -4517,6 +4517,10 @@ pref("image.animated.decode-on-demand.threshold-kb", 4194303);
// animation's currently displayed frame.
pref("image.animated.decode-on-demand.batch-size", 6);
// Resume an animated image from the last displayed frame rather than
// advancing when out of view.
pref("image.animated.resume-from-last-displayed", true);
// Maximum number of surfaces for an image before entering "factor of 2" mode.
// This in addition to the number of "native" sizes of an image. A native size
// is a size for which we can decode a frame without up or downscaling. Most