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 310202 02f9e27b988dd63dce023fbca505ac74a1e8a1f6
parent 310201 6d4e430fbd45ed519c81bc6fde02dd9e31d56251
child 310203 cfb1f1eeceb36952195fd255d5a7a5f017771cbd
push id30576
push userryanvm@gmail.com
push dateFri, 19 Aug 2016 13:53:39 +0000
treeherdermozilla-central@74f332c38a69 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert, edwin
bugs1293472
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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',