/* -*- 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 "AnimationSurfaceProvider.h" #include "gfxPrefs.h" #include "mozilla/gfx/gfxVars.h" #include "nsProxyRelease.h" #include "DecodePool.h" #include "Decoder.h" using namespace mozilla::gfx; namespace mozilla { namespace image { AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull aImage, const SurfaceKey& aSurfaceKey, NotNull aDecoder, size_t aCurrentFrame) : ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey, AvailabilityState::StartAsPlaceholder()) , mImage(aImage.get()) , mDecodingMutex("AnimationSurfaceProvider::mDecoder") , mDecoder(aDecoder.get()) , mFramesMutex("AnimationSurfaceProvider::mFrames") { MOZ_ASSERT(!mDecoder->IsMetadataDecode(), "Use MetadataDecodingTask for metadata decodes"); MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(), "Use DecodedSurfaceProvider for single-frame image decodes"); // We may produce paletted surfaces for GIF which means the frames are smaller // than one would expect. size_t pixelSize = !aDecoder->ShouldBlendAnimation() && 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.reset(new AnimationFrameRetainedBuffer(threshold, batch, aCurrentFrame)); } AnimationSurfaceProvider::~AnimationSurfaceProvider() { DropImageReference(); if (mDecoder) { mDecoder->SetFrameRecycler(nullptr); } } void AnimationSurfaceProvider::DropImageReference() { if (!mImage) { return; // Nothing to do. } // RasterImage objects need to be destroyed on the main thread. NS_ReleaseOnMainThreadSystemGroup("AnimationSurfaceProvider::mImage", 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) { MutexAutoLock lock(mFramesMutex); if (Availability().IsPlaceholder()) { MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() on a placeholder"); return DrawableFrameRef(); } imgFrame* frame = mFrames->Get(aFrame, /* aForDisplay */ true); if (!frame) { return DrawableFrameRef(); } return frame->DrawableRef(); } already_AddRefed AnimationSurfaceProvider::GetFrame(size_t aFrame) { MutexAutoLock lock(mFramesMutex); if (Availability().IsPlaceholder()) { MOZ_ASSERT_UNREACHABLE("Calling GetFrame() on a placeholder"); return nullptr; } RefPtr frame = mFrames->Get(aFrame, /* aForDisplay */ false); MOZ_ASSERT_IF(frame, frame->IsFinished()); return frame.forget(); } bool AnimationSurfaceProvider::IsFinished() const { MutexAutoLock lock(mFramesMutex); if (Availability().IsPlaceholder()) { MOZ_ASSERT_UNREACHABLE("Calling IsFinished() on a placeholder"); return false; } return mFrames->IsFirstFrameFinished(); } bool AnimationSurfaceProvider::IsFullyDecoded() const { MutexAutoLock lock(mFramesMutex); return mFrames->SizeKnown() && !mFrames->MayDiscard(); } size_t AnimationSurfaceProvider::LogicalSizeInBytes() const { // When decoding animated images, we need at most three live surfaces: the // composited surface, the previous composited surface for // DisposalMethod::RESTORE_PREVIOUS, and the surface we're currently decoding // into. The composited surfaces are always BGRA. Although the surface we're // decoding into may be paletted, and may be smaller than the real size of the // image, we assume the worst case here. // XXX(seth): Note that this is actually not accurate yet; we're storing the // full sequence of frames, not just the three live surfaces mentioned above. // Unfortunately there's no way to know in advance how many frames an // animation has, so we really can't do better here. This will become correct // once bug 1289954 is complete. IntSize size = GetSurfaceKey().Size(); return 3 * size.width * size.height * sizeof(uint32_t); } void AnimationSurfaceProvider::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, const AddSizeOfCb& aCallback) { // Note that the surface cache lock is already held here, and then we acquire // mFramesMutex. For this method, this ordering is unavoidable, which means // that we must be careful to always use the same ordering elsewhere. MutexAutoLock lock(mFramesMutex); mFrames->AddSizeOfExcludingThis(aMallocSizeOf, aCallback); } void AnimationSurfaceProvider::Run() { MutexAutoLock lock(mDecodingMutex); if (!mDecoder) { MOZ_ASSERT_UNREACHABLE("Running after decoding finished?"); return; } while (true) { // Run the decoder. LexerResult result = mDecoder->Decode(WrapNotNull(this)); if (result.is()) { // We may have a new frame now, but it's not guaranteed - a decoding // failure or truncated data may mean that no new frame got produced. // Since we're not sure, rather than call CheckForNewFrameAtYield() here // we call CheckForNewFrameAtTerminalState(), which handles both of these // possibilities. bool continueDecoding = CheckForNewFrameAtTerminalState(); 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; } // Restart from the very beginning because the decoder was recreated. continue; } // Notify for the progress we've made so far. if (mImage && mDecoder->HasProgress()) { NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder)); } if (result == LexerResult(Yield::NEED_MORE_DATA)) { // We can't make any more progress right now. The decoder itself will ensure // that we get reenqueued when more data is available; just return for now. return; } // 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)); if (!CheckForNewFrameAtYield() || DecodePool::Singleton()->IsShuttingDown()) { return; } } } bool AnimationSurfaceProvider::CheckForNewFrameAtYield() { mDecodingMutex.AssertCurrentThreadOwns(); MOZ_ASSERT(mDecoder); bool justGotFirstFrame = false; bool continueDecoding = false; { MutexAutoLock lock(mFramesMutex); // Try to get the new frame from the decoder. RefPtr frame = mDecoder->GetCurrentFrame(); MOZ_ASSERT(mDecoder->HasFrameToTake()); mDecoder->ClearHasFrameToTake(); if (!frame) { MOZ_ASSERT_UNREACHABLE("Decoder yielded but didn't produce a frame?"); return true; } // We should've gotten a different frame than last time. MOZ_ASSERT(!mFrames->IsLastInsertedFrame(frame)); // Append the new frame to the list. AnimationFrameBuffer::InsertStatus status = mFrames->Insert(std::move(frame)); switch (status) { case AnimationFrameBuffer::InsertStatus::DISCARD_CONTINUE: continueDecoding = true; MOZ_FALLTHROUGH; case AnimationFrameBuffer::InsertStatus::DISCARD_YIELD: RequestFrameDiscarding(); break; case AnimationFrameBuffer::InsertStatus::CONTINUE: continueDecoding = true; break; case AnimationFrameBuffer::InsertStatus::YIELD: break; default: MOZ_ASSERT_UNREACHABLE("Unhandled insert status!"); break; } // 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->Size(); if (frameCount == 1 && mImage) { justGotFirstFrame = true; } } if (justGotFirstFrame) { AnnounceSurfaceAvailable(); } return continueDecoding; } 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. RefPtr frame = mDecoder->GetCurrentFrame(); // 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 = nullptr; } else { MOZ_ASSERT(frame); mDecoder->ClearHasFrameToTake(); } if (!frame || mFrames->IsLastInsertedFrame(frame)) { return mFrames->MarkComplete(mDecoder->GetFirstFrameRefreshArea()); } // Append the new frame to the list. AnimationFrameBuffer::InsertStatus status = mFrames->Insert(std::move(frame)); switch (status) { case AnimationFrameBuffer::InsertStatus::DISCARD_CONTINUE: case AnimationFrameBuffer::InsertStatus::DISCARD_YIELD: RequestFrameDiscarding(); break; case AnimationFrameBuffer::InsertStatus::CONTINUE: case AnimationFrameBuffer::InsertStatus::YIELD: break; default: MOZ_ASSERT_UNREACHABLE("Unhandled insert status!"); break; } continueDecoding = mFrames->MarkComplete(mDecoder->GetFirstFrameRefreshArea()); // 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->Size() == 1 && mImage) { justGotFirstFrame = true; } } if (justGotFirstFrame) { AnnounceSurfaceAvailable(); } return continueDecoding; } void AnimationSurfaceProvider::RequestFrameDiscarding() { mDecodingMutex.AssertCurrentThreadOwns(); mFramesMutex.AssertCurrentThreadOwns(); MOZ_ASSERT(mDecoder); if (mFrames->MayDiscard() || mFrames->IsRecycling()) { MOZ_ASSERT_UNREACHABLE("Already replaced frame queue!"); return; } auto oldFrameQueue = static_cast(mFrames.get()); // We only recycle if it is a full frame. Partial frames may be sized // differently from each other. We do not support recycling with WebRender // and shared surfaces at this time as there is additional synchronization // required to know when it is safe to recycle. MOZ_ASSERT(!mDecoder->GetFrameRecycler()); if (gfxPrefs::ImageAnimatedDecodeOnDemandRecycle() && mDecoder->ShouldBlendAnimation() && (!gfxVars::GetUseWebRenderOrDefault() || !gfxPrefs::ImageMemShared())) { mFrames.reset(new AnimationFrameRecyclingQueue(std::move(*oldFrameQueue))); mDecoder->SetFrameRecycler(this); } else { mFrames.reset(new AnimationFrameDiscardingQueue(std::move(*oldFrameQueue))); } } void AnimationSurfaceProvider::AnnounceSurfaceAvailable() { mFramesMutex.AssertNotCurrentThreadOwns(); MOZ_ASSERT(mImage); // We just got the first frame; let the surface cache know. We deliberately do // this outside of mFramesMutex to avoid a potential deadlock with // AddSizeOfExcludingThis(), since otherwise we'd be acquiring mFramesMutex // and then the surface cache lock, while the memory reporting code would // acquire the surface cache lock and then mFramesMutex. SurfaceCache::SurfaceAvailable(WrapNotNull(this)); } void AnimationSurfaceProvider::FinishDecoding() { mDecodingMutex.AssertCurrentThreadOwns(); MOZ_ASSERT(mDecoder); if (mImage) { // Send notifications. NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder)); } // 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; } // 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 // finishes. If we don't drop our reference to the image, we'll end up // keeping it alive as long as we remain in the surface cache, which could // greatly extend the image's lifetime - in fact, if the image isn't // discardable, it'd result in a leak! DropImageReference(); } bool AnimationSurfaceProvider::ShouldPreferSyncRun() const { MutexAutoLock lock(mDecodingMutex); MOZ_ASSERT(mDecoder); return mDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime()); } RawAccessFrameRef AnimationSurfaceProvider::RecycleFrame(gfx::IntRect& aRecycleRect) { MutexAutoLock lock(mFramesMutex); MOZ_ASSERT(mFrames->IsRecycling()); return mFrames->RecycleFrame(aRecycleRect); } } // namespace image } // namespace mozilla