diff options
Diffstat (limited to 'image/DecodedSurfaceProvider.cpp')
-rw-r--r-- | image/DecodedSurfaceProvider.cpp | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/image/DecodedSurfaceProvider.cpp b/image/DecodedSurfaceProvider.cpp new file mode 100644 index 000000000..45b4849d2 --- /dev/null +++ b/image/DecodedSurfaceProvider.cpp @@ -0,0 +1,224 @@ +/* -*- 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 "gfxPrefs.h" +#include "nsProxyRelease.h" + +#include "Decoder.h" + +using namespace mozilla::gfx; + +namespace mozilla { +namespace image { + +DecodedSurfaceProvider::DecodedSurfaceProvider(NotNull<RasterImage*> aImage, + const SurfaceKey& aSurfaceKey, + NotNull<Decoder*> 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<RasterImage> image = mImage; + mImage = nullptr; + NS_ReleaseOnMainThread(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.width * 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<TerminalState>()) { + 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<Yield>()); + + 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)); + + // 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(gfxPrefs::ImageMemDecodeBytesAtATime()); +} + +} // namespace image +} // namespace mozilla |