diff options
Diffstat (limited to '')
-rw-r--r-- | image/DecodedSurfaceProvider.cpp | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/image/DecodedSurfaceProvider.cpp b/image/DecodedSurfaceProvider.cpp new file mode 100644 index 0000000000..d91a17a58c --- /dev/null +++ b/image/DecodedSurfaceProvider.cpp @@ -0,0 +1,209 @@ +/* -*- 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<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; + SurfaceCache::ReleaseImageOnMainThread(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<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)); + + // 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 |