summaryrefslogtreecommitdiffstats
path: root/image/AnimationSurfaceProvider.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'image/AnimationSurfaceProvider.cpp')
-rw-r--r--image/AnimationSurfaceProvider.cpp291
1 files changed, 291 insertions, 0 deletions
diff --git a/image/AnimationSurfaceProvider.cpp b/image/AnimationSurfaceProvider.cpp
new file mode 100644
index 000000000..0dacf25c2
--- /dev/null
+++ b/image/AnimationSurfaceProvider.cpp
@@ -0,0 +1,291 @@
+/* -*- 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 "nsProxyRelease.h"
+
+#include "Decoder.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace image {
+
+AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
+ const SurfaceKey& aSurfaceKey,
+ NotNull<Decoder*> aDecoder)
+ : 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");
+}
+
+AnimationSurfaceProvider::~AnimationSurfaceProvider()
+{
+ DropImageReference();
+}
+
+void
+AnimationSurfaceProvider::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
+AnimationSurfaceProvider::DrawableRef(size_t aFrame)
+{
+ MutexAutoLock lock(mFramesMutex);
+
+ if (Availability().IsPlaceholder()) {
+ MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() on a placeholder");
+ 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();
+}
+
+bool
+AnimationSurfaceProvider::IsFinished() const
+{
+ MutexAutoLock lock(mFramesMutex);
+
+ if (Availability().IsPlaceholder()) {
+ MOZ_ASSERT_UNREACHABLE("Calling IsFinished() on a placeholder");
+ return false;
+ }
+
+ if (mFrames.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();
+}
+
+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,
+ size_t& aHeapSizeOut,
+ size_t& aNonHeapSizeOut)
+{
+ // 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);
+
+ for (const RawAccessFrameRef& frame : mFrames) {
+ frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut);
+ }
+}
+
+void
+AnimationSurfaceProvider::Run()
+{
+ MutexAutoLock lock(mDecodingMutex);
+
+ if (!mDecoder || !mImage) {
+ MOZ_ASSERT_UNREACHABLE("Running after decoding finished?");
+ return;
+ }
+
+ while (true) {
+ // Run the decoder.
+ LexerResult result = mDecoder->Decode(WrapNotNull(this));
+
+ if (result.is<TerminalState>()) {
+ // 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.
+ CheckForNewFrameAtTerminalState();
+
+ // We're done!
+ FinishDecoding();
+ return;
+ }
+
+ // Notify for the progress we've made so far.
+ if (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.
+ MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE));
+ CheckForNewFrameAtYield();
+ }
+}
+
+void
+AnimationSurfaceProvider::CheckForNewFrameAtYield()
+{
+ mDecodingMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mDecoder);
+
+ bool justGotFirstFrame = false;
+
+ {
+ MutexAutoLock lock(mFramesMutex);
+
+ // Try to get the new frame from the decoder.
+ RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
+ if (!frame) {
+ MOZ_ASSERT_UNREACHABLE("Decoder yielded but didn't produce a frame?");
+ return;
+ }
+
+ // We should've gotten a different frame than last time.
+ MOZ_ASSERT_IF(!mFrames.IsEmpty(),
+ mFrames.LastElement().get() != frame.get());
+
+ // Append the new frame to the list.
+ mFrames.AppendElement(Move(frame));
+
+ if (mFrames.Length() == 1) {
+ justGotFirstFrame = true;
+ }
+ }
+
+ if (justGotFirstFrame) {
+ AnnounceSurfaceAvailable();
+ }
+}
+
+void
+AnimationSurfaceProvider::CheckForNewFrameAtTerminalState()
+{
+ mDecodingMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mDecoder);
+
+ bool justGotFirstFrame = false;
+
+ {
+ MutexAutoLock lock(mFramesMutex);
+
+ RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
+ if (!frame) {
+ return;
+ }
+
+ if (!mFrames.IsEmpty() && mFrames.LastElement().get() == frame.get()) {
+ return; // We already have this one.
+ }
+
+ // Append the new frame to the list.
+ mFrames.AppendElement(Move(frame));
+
+ if (mFrames.Length() == 1) {
+ justGotFirstFrame = true;
+ }
+ }
+
+ if (justGotFirstFrame) {
+ AnnounceSurfaceAvailable();
+ }
+}
+
+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(mImage);
+ MOZ_ASSERT(mDecoder);
+
+ // Send notifications.
+ NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
+
+ // Destroy our decoder; we don't need it anymore.
+ 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());
+}
+
+} // namespace image
+} // namespace mozilla