From 39b30d06d42d3a79acc91394fac4a30dac25bbd4 Mon Sep 17 00:00:00 2001 From: Andrew Osmond Date: Wed, 28 Feb 2018 13:34:53 -0500 Subject: [PATCH] Bug 523950 - Part 9. Integrate AnimationSurfaceProvider with AnimationFrameBuffer. r=tnikkel --- image/AnimationSurfaceProvider.cpp | 193 ++++++++++++++++++++++------- image/AnimationSurfaceProvider.h | 14 ++- 2 files changed, 160 insertions(+), 47 deletions(-) diff --git a/image/AnimationSurfaceProvider.cpp b/image/AnimationSurfaceProvider.cpp index 094d6dce1aea..407001219a56 100644 --- a/image/AnimationSurfaceProvider.cpp +++ b/image/AnimationSurfaceProvider.cpp @@ -8,6 +8,7 @@ #include "gfxPrefs.h" #include "nsProxyRelease.h" +#include "DecodePool.h" #include "Decoder.h" using namespace mozilla::gfx; @@ -30,6 +31,22 @@ AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull aImage, "Use MetadataDecodingTask for metadata decodes"); MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(), "Use DecodedSurfaceProvider for single-frame image decodes"); + + // We still produce paletted surfaces for GIF which means the frames are + // smaller than one would expect for APNG. This may be removed if/when + // bug 1337111 lands and it is enabled by default. + size_t pixelSize = aDecoder->GetType() == DecoderType::GIF + ? sizeof(uint8_t) : sizeof(uint32_t); + + // Calculate how many frames we need to decode in this animation before we + // enter decode-on-demand mode. + IntSize frameSize = aSurfaceKey.Size(); + size_t threshold = + (size_t(gfxPrefs::ImageAnimatedDecodeOnDemandThresholdKB()) * 1024) / + (pixelSize * frameSize.width * frameSize.height); + size_t batch = gfxPrefs::ImageAnimatedDecodeOnDemandBatchSize(); + + mFrames.Initialize(threshold, batch, aCurrentFrame); } AnimationSurfaceProvider::~AnimationSurfaceProvider() @@ -49,6 +66,64 @@ AnimationSurfaceProvider::DropImageReference() mImage.forget()); } +void +AnimationSurfaceProvider::Reset() +{ + // We want to go back to the beginning. + bool mayDiscard; + bool restartDecoder; + + { + 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); + + // Recreate the decoder so we can regenerate the frames again. + mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder); + MOZ_ASSERT(mDecoder); + + MutexAutoLock lock2(mFramesMutex); + restartDecoder = mFrames.Reset(); + } + + if (restartDecoder) { + DecodePool::Singleton()->AsyncRun(this); + } +} + +void +AnimationSurfaceProvider::Advance(size_t aFrame) +{ + bool restartDecoder; + + { + // Typical advancement of a frame. + MutexAutoLock lock(mFramesMutex); + restartDecoder = mFrames.AdvanceTo(aFrame); + } + + if (restartDecoder) { + DecodePool::Singleton()->AsyncRun(this); + } +} + DrawableFrameRef AnimationSurfaceProvider::DrawableRef(size_t aFrame) { @@ -59,19 +134,7 @@ AnimationSurfaceProvider::DrawableRef(size_t aFrame) return DrawableFrameRef(); } - if (mFrames.IsEmpty()) { - MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() when we have no frames"); - return DrawableFrameRef(); - } - - // If we don't have that frame, return an empty frame ref. - if (aFrame >= mFrames.Length()) { - return DrawableFrameRef(); - } - - // We've got the requested frame. Return it. - MOZ_ASSERT(mFrames[aFrame]); - return mFrames[aFrame]->DrawableRef(); + return mFrames.Get(aFrame); } bool @@ -84,13 +147,20 @@ AnimationSurfaceProvider::IsFinished() const return false; } - if (mFrames.IsEmpty()) { + if (mFrames.Frames().IsEmpty()) { MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no frames"); return false; } // As long as we have at least one finished frame, we're finished. - return mFrames[0]->IsFinished(); + return mFrames.Frames()[0]->IsFinished(); +} + +bool +AnimationSurfaceProvider::IsFullyDecoded() const +{ + MutexAutoLock lock(mFramesMutex); + return mFrames.SizeKnown() && !mFrames.MayDiscard(); } size_t @@ -122,9 +192,11 @@ AnimationSurfaceProvider::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, // that we must be careful to always use the same ordering elsewhere. MutexAutoLock lock(mFramesMutex); - for (const RawAccessFrameRef& frame : mFrames) { - frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, - aNonHeapSizeOut, aExtHandlesOut); + for (const RawAccessFrameRef& frame : mFrames.Frames()) { + if (frame) { + frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, + aNonHeapSizeOut, aExtHandlesOut); + } } } @@ -133,7 +205,7 @@ AnimationSurfaceProvider::Run() { MutexAutoLock lock(mDecodingMutex); - if (!mDecoder || !mImage) { + if (!mDecoder) { MOZ_ASSERT_UNREACHABLE("Running after decoding finished?"); return; } @@ -148,15 +220,21 @@ AnimationSurfaceProvider::Run() // Since we're not sure, rather than call CheckForNewFrameAtYield() here // we call CheckForNewFrameAtTerminalState(), which handles both of these // possibilities. - CheckForNewFrameAtTerminalState(); - - // We're done! + bool continueDecoding = CheckForNewFrameAtTerminalState(); FinishDecoding(); + + // Even if it is the last frame, we may not have enough frames buffered + // ahead of the current. + if (continueDecoding) { + MOZ_ASSERT(mDecoder); + continue; + } + return; } // Notify for the progress we've made so far. - if (mDecoder->HasProgress()) { + if (mImage && mDecoder->HasProgress()) { NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder)); } @@ -166,19 +244,23 @@ AnimationSurfaceProvider::Run() return; } - // There's new output available - a new frame! Grab it. + // There's new output available - a new frame! Grab it. If we don't need any + // more for the moment we can break out of the loop. MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE)); - CheckForNewFrameAtYield(); + if (!CheckForNewFrameAtYield()) { + return; + } } } -void +bool AnimationSurfaceProvider::CheckForNewFrameAtYield() { mDecodingMutex.AssertCurrentThreadOwns(); MOZ_ASSERT(mDecoder); bool justGotFirstFrame = false; + bool continueDecoding; { MutexAutoLock lock(mFramesMutex); @@ -187,17 +269,20 @@ AnimationSurfaceProvider::CheckForNewFrameAtYield() RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef(); if (!frame) { MOZ_ASSERT_UNREACHABLE("Decoder yielded but didn't produce a frame?"); - return; + return true; } // We should've gotten a different frame than last time. - MOZ_ASSERT_IF(!mFrames.IsEmpty(), - mFrames.LastElement().get() != frame.get()); + MOZ_ASSERT_IF(!mFrames.Frames().IsEmpty(), + mFrames.Frames().LastElement().get() != frame.get()); // Append the new frame to the list. - mFrames.AppendElement(Move(frame)); + continueDecoding = mFrames.Insert(Move(frame)); - if (mFrames.Length() == 1) { + // We only want to handle the first frame if it is the first pass for the + // animation decoder. The owning image will be cleared after that. + size_t frameCount = mFrames.Frames().Length(); + if (frameCount == 1 && mImage) { justGotFirstFrame = true; } } @@ -205,32 +290,37 @@ AnimationSurfaceProvider::CheckForNewFrameAtYield() if (justGotFirstFrame) { AnnounceSurfaceAvailable(); } + + return continueDecoding; } -void +bool AnimationSurfaceProvider::CheckForNewFrameAtTerminalState() { mDecodingMutex.AssertCurrentThreadOwns(); MOZ_ASSERT(mDecoder); bool justGotFirstFrame = false; + bool continueDecoding; { MutexAutoLock lock(mFramesMutex); + // The decoder may or may not have a new frame for us at this point. Avoid + // reinserting the same frame again. RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef(); - if (!frame) { - return; - } - - if (!mFrames.IsEmpty() && mFrames.LastElement().get() == frame.get()) { - return; // We already have this one. + if (!frame || (!mFrames.Frames().IsEmpty() && + mFrames.Frames().LastElement().get() == frame.get())) { + return mFrames.MarkComplete(); } // Append the new frame to the list. - mFrames.AppendElement(Move(frame)); + mFrames.Insert(Move(frame)); + continueDecoding = mFrames.MarkComplete(); - if (mFrames.Length() == 1) { + // We only want to handle the first frame if it is the first pass for the + // animation decoder. The owning image will be cleared after that. + if (mFrames.Frames().Length() == 1 && mImage) { justGotFirstFrame = true; } } @@ -238,6 +328,8 @@ AnimationSurfaceProvider::CheckForNewFrameAtTerminalState() if (justGotFirstFrame) { AnnounceSurfaceAvailable(); } + + return continueDecoding; } void @@ -258,14 +350,27 @@ void AnimationSurfaceProvider::FinishDecoding() { mDecodingMutex.AssertCurrentThreadOwns(); - MOZ_ASSERT(mImage); MOZ_ASSERT(mDecoder); - // Send notifications. - NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder)); + if (mImage) { + // Send notifications. + NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder)); + } // Destroy our decoder; we don't need it anymore. - mDecoder = nullptr; + bool mayDiscard; + { + MutexAutoLock lock(mFramesMutex); + mayDiscard = mFrames.MayDiscard(); + } + + if (mayDiscard) { + // Recreate the decoder so we can regenerate the frames again. + mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder); + MOZ_ASSERT(mDecoder); + } else { + mDecoder = nullptr; + } // We don't need a reference to our image anymore, either, and we don't want // one. We may be stored in the surface cache for a long time after decoding diff --git a/image/AnimationSurfaceProvider.h b/image/AnimationSurfaceProvider.h index 4b291d0c6803..4853009a1a5b 100644 --- a/image/AnimationSurfaceProvider.h +++ b/image/AnimationSurfaceProvider.h @@ -13,6 +13,7 @@ #include "FrameAnimator.h" #include "IDecodingTask.h" #include "ISurfaceProvider.h" +#include "AnimationFrameBuffer.h" namespace mozilla { namespace image { @@ -45,11 +46,14 @@ public: DrawableSurface Surface() override { return DrawableSurface(WrapNotNull(this)); } bool IsFinished() const override; + bool IsFullyDecoded() const override; size_t LogicalSizeInBytes() const override; void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, size_t& aHeapSizeOut, size_t& aNonHeapSizeOut, size_t& aExtHandlesOut) override; + void Reset() override; + void Advance(size_t aFrame) override; protected: DrawableFrameRef DrawableRef(size_t aFrame) override; @@ -79,11 +83,15 @@ private: virtual ~AnimationSurfaceProvider(); void DropImageReference(); - void CheckForNewFrameAtYield(); - void CheckForNewFrameAtTerminalState(); void AnnounceSurfaceAvailable(); void FinishDecoding(); + // @returns Whether or not we should continue decoding. + bool CheckForNewFrameAtYield(); + + // @returns Whether or not we should restart decoding. + bool CheckForNewFrameAtTerminalState(); + /// The image associated with our decoder. RefPtr mImage; @@ -97,7 +105,7 @@ private: mutable Mutex mFramesMutex; /// The frames of this animation, in order. - nsTArray mFrames; + AnimationFrameBuffer mFrames; }; } // namespace image