Bug 1293472 (Part 2) - Add AnimationSurfaceProvider. r=dholbert,edwin
authorSeth Fowler <mark.seth.fowler@gmail.com>
Thu, 18 Aug 2016 00:01:10 -0700
changeset 402995 02f9e27b988dd63dce023fbca505ac74a1e8a1f6
parent 402994 6d4e430fbd45ed519c81bc6fde02dd9e31d56251
child 402996 cfb1f1eeceb36952195fd255d5a7a5f017771cbd
push id26791
push userbmo:mh+mozilla@glandium.org
push dateFri, 19 Aug 2016 00:08:51 +0000
reviewersdholbert, edwin
bugs1293472
milestone51.0a1
Bug 1293472 (Part 2) - Add AnimationSurfaceProvider. r=dholbert,edwin
image/AnimationSurfaceProvider.cpp
image/AnimationSurfaceProvider.h
image/moz.build
new file mode 100644
--- /dev/null
+++ b/image/AnimationSurfaceProvider.cpp
@@ -0,0 +1,274 @@
+/* -*- 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,
+                                                   NotNull<Decoder*> aDecoder,
+                                                   const SurfaceKey& aSurfaceKey)
+  : ISurfaceProvider(AvailabilityState::StartAsPlaceholder())
+  , mImage(aImage.get())
+  , mDecodingMutex("AnimationSurfaceProvider::mDecoder")
+  , mDecoder(aDecoder.get())
+  , mFramesMutex("AnimationSurfaceProvider::mFrames")
+  , mSurfaceKey(aSurfaceKey)
+{
+  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 = mSurfaceKey.Size();
+  return 3 * size.width * size.height * sizeof(uint32_t);
+}
+
+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.
+  SurfaceCache::SurfaceAvailable(WrapNotNull(this),
+                                 ImageKey(mImage.get()),
+                                 mSurfaceKey);
+}
+
+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
new file mode 100644
--- /dev/null
+++ b/image/AnimationSurfaceProvider.h
@@ -0,0 +1,104 @@
+/* -*- 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/. */
+
+/**
+ * An ISurfaceProvider for animated images.
+ */
+
+#ifndef mozilla_image_AnimationSurfaceProvider_h
+#define mozilla_image_AnimationSurfaceProvider_h
+
+#include "FrameAnimator.h"
+#include "IDecodingTask.h"
+#include "ISurfaceProvider.h"
+
+namespace mozilla {
+namespace image {
+
+/**
+ * An ISurfaceProvider that manages the decoding of animated images and
+ * dynamically generates surfaces for the current playback state of the
+ * animation.
+ */
+class AnimationSurfaceProvider final
+  : public ISurfaceProvider
+  , public IDecodingTask
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnimationSurfaceProvider, override)
+
+  AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
+                           NotNull<Decoder*> aDecoder,
+                           const SurfaceKey& aSurfaceKey);
+
+
+  //////////////////////////////////////////////////////////////////////////////
+  // ISurfaceProvider implementation.
+  //////////////////////////////////////////////////////////////////////////////
+
+public:
+  // We use the ISurfaceProvider constructor of DrawableSurface to indicate that
+  // our surfaces are computed lazily.
+  DrawableSurface Surface() override { return DrawableSurface(WrapNotNull(this)); }
+
+  bool IsFinished() const override;
+  size_t LogicalSizeInBytes() const override;
+
+protected:
+  DrawableFrameRef DrawableRef(size_t aFrame) override;
+
+  // Animation frames are always locked. This is because we only want to release
+  // their memory atomically (due to the surface cache discarding them). If they
+  // were unlocked, the OS could end up releasing the memory of random frames
+  // from the middle of the animation, which is not worth the complexity of
+  // dealing with.
+  bool IsLocked() const override { return true; }
+  void SetLocked(bool) override { }
+
+
+  //////////////////////////////////////////////////////////////////////////////
+  // IDecodingTask implementation.
+  //////////////////////////////////////////////////////////////////////////////
+
+public:
+  void Run() override;
+  bool ShouldPreferSyncRun() const override;
+
+  // Full decodes are low priority compared to metadata decodes because they
+  // don't block layout or page load.
+  TaskPriority Priority() const override { return TaskPriority::eLow; }
+
+private:
+  virtual ~AnimationSurfaceProvider();
+
+  void DropImageReference();
+  void CheckForNewFrameAtYield();
+  void CheckForNewFrameAtTerminalState();
+  void AnnounceSurfaceAvailable();
+  void FinishDecoding();
+
+  /// The image associated with our decoder.
+  RefPtr<RasterImage> mImage;
+
+  /// A mutex to protect mDecoder. Always taken before mFramesMutex.
+  mutable Mutex mDecodingMutex;
+
+  /// The decoder used to decode this animation.
+  RefPtr<Decoder> mDecoder;
+
+  /// A mutex to protect mFrames. Always taken after mDecodingMutex.
+  mutable Mutex mFramesMutex;
+
+  /// The frames of this animation, in order.
+  nsTArray<RawAccessFrameRef> mFrames;
+
+  /// The key under which we're stored as a cache entry in the surface cache.
+  const SurfaceKey mSurfaceKey;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_AnimationSurfaceProvider_h
--- a/image/moz.build
+++ b/image/moz.build
@@ -45,16 +45,17 @@ EXPORTS += [
     'imgRequest.h',
     'imgRequestProxy.h',
     'IProgressObserver.h',
     'Orientation.h',
     'SurfaceCacheUtils.h',
 ]
 
 UNIFIED_SOURCES += [
+    'AnimationSurfaceProvider.cpp',
     'ClippedImage.cpp',
     'DecodedSurfaceProvider.cpp',
     'DecodePool.cpp',
     'Decoder.cpp',
     'DecoderFactory.cpp',
     'DynamicImage.cpp',
     'FrameAnimator.cpp',
     'FrozenImage.cpp',