/* -*- 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 "DecodedSurfaceProvider.h" #include "mozilla/StaticPrefs_image.h" #include "nsProxyRelease.h" #include "Decoder.h" using namespace mozilla::gfx; namespace mozilla { namespace image { DecodedSurfaceProvider::DecodedSurfaceProvider(NotNull aImage, const SurfaceKey& aSurfaceKey, NotNull aDecoder) : ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey, AvailabilityState::StartAsPlaceholder()), mImage(aImage.get()), mMutex("mozilla::image::DecodedSurfaceProvider"), mDecoder(aDecoder.get()) { MOZ_ASSERT(!mDecoder->IsMetadataDecode(), "Use MetadataDecodingTask for metadata decodes"); MOZ_ASSERT(mDecoder->IsFirstFrameDecode(), "Use AnimationSurfaceProvider for animation decodes"); } DecodedSurfaceProvider::~DecodedSurfaceProvider() { DropImageReference(); } void DecodedSurfaceProvider::DropImageReference() { if (!mImage) { return; // Nothing to do. } // RasterImage objects need to be destroyed on the main thread. We also need // to destroy them asynchronously, because if our surface cache entry is // destroyed and we were the only thing keeping |mImage| alive, RasterImage's // destructor may call into the surface cache while whatever code caused us to // get evicted is holding the surface cache lock, causing deadlock. RefPtr image = mImage; mImage = nullptr; NS_ReleaseOnMainThreadSystemGroup(image.forget(), /* aAlwaysProxy = */ true); } DrawableFrameRef DecodedSurfaceProvider::DrawableRef(size_t aFrame) { MOZ_ASSERT(aFrame == 0, "Requesting an animation frame from a DecodedSurfaceProvider?"); // We depend on SurfaceCache::SurfaceAvailable() to provide synchronization // for methods that touch |mSurface|; after SurfaceAvailable() is called, // |mSurface| should be non-null and shouldn't be mutated further until we get // destroyed. That means that the assertions below are very important; we'll // end up with data races if these assumptions are violated. if (Availability().IsPlaceholder()) { MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() on a placeholder"); return DrawableFrameRef(); } if (!mSurface) { MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() when we have no surface"); return DrawableFrameRef(); } return mSurface->DrawableRef(); } bool DecodedSurfaceProvider::IsFinished() const { // See DrawableRef() for commentary on these assertions. if (Availability().IsPlaceholder()) { MOZ_ASSERT_UNREACHABLE("Calling IsFinished() on a placeholder"); return false; } if (!mSurface) { MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no surface"); return false; } return mSurface->IsFinished(); } void DecodedSurfaceProvider::SetLocked(bool aLocked) { // See DrawableRef() for commentary on these assertions. if (Availability().IsPlaceholder()) { MOZ_ASSERT_UNREACHABLE("Calling SetLocked() on a placeholder"); return; } if (!mSurface) { MOZ_ASSERT_UNREACHABLE("Calling SetLocked() when we have no surface"); return; } if (aLocked == IsLocked()) { return; // Nothing to do. } // If we're locked, hold a DrawableFrameRef to |mSurface|, which will keep any // volatile buffer it owns in memory. mLockRef = aLocked ? mSurface->DrawableRef() : DrawableFrameRef(); } size_t DecodedSurfaceProvider::LogicalSizeInBytes() const { // Single frame images are always 32bpp. IntSize size = GetSurfaceKey().Size(); return size_t(size.width) * size_t(size.height) * sizeof(uint32_t); } void DecodedSurfaceProvider::Run() { MutexAutoLock lock(mMutex); if (!mDecoder || !mImage) { MOZ_ASSERT_UNREACHABLE("Running after decoding finished?"); return; } // Run the decoder. LexerResult result = mDecoder->Decode(WrapNotNull(this)); // If there's a new surface available, announce it to the surface cache. CheckForNewSurface(); if (result.is()) { FinishDecoding(); return; // We're done. } // Notify for the progress we've made so far. if (mDecoder->HasProgress()) { NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder)); } MOZ_ASSERT(result.is()); 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; } // Single-frame images shouldn't yield for any reason except NEED_MORE_DATA. MOZ_ASSERT_UNREACHABLE("Unexpected yield for single-frame image"); mDecoder->TerminateFailure(); FinishDecoding(); } void DecodedSurfaceProvider::CheckForNewSurface() { mMutex.AssertCurrentThreadOwns(); MOZ_ASSERT(mDecoder); if (mSurface) { // Single-frame images should produce no more than one surface, so if we // have one, it should be the same one the decoder is working on. MOZ_ASSERT(mSurface.get() == mDecoder->GetCurrentFrameRef().get(), "DecodedSurfaceProvider and Decoder have different surfaces?"); return; } // We don't have a surface yet; try to get one from the decoder. mSurface = mDecoder->GetCurrentFrameRef().get(); if (!mSurface) { return; // No surface yet. } // We just got a surface for the first time; let the surface cache know. MOZ_ASSERT(mImage); SurfaceCache::SurfaceAvailable(WrapNotNull(this)); } void DecodedSurfaceProvider::FinishDecoding() { mMutex.AssertCurrentThreadOwns(); MOZ_ASSERT(mImage); MOZ_ASSERT(mDecoder); // Send notifications. NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder)); // If we have a new and complete surface, we can try to prune similarly sized // surfaces if the cache supports it. if (mSurface && mSurface->IsFinished()) { SurfaceCache::PruneImage(ImageKey(mImage)); } // Destroy our decoder; we don't need it anymore. (And if we don't destroy it, // our surface can never be optimized, because the decoder has a // RawAccessFrameRef to it.) 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 DecodedSurfaceProvider::ShouldPreferSyncRun() const { return mDecoder->ShouldSyncDecode( StaticPrefs::image_mem_decode_bytes_at_a_time_AtStartup()); } } // namespace image } // namespace mozilla