Bug 1069652 - Optimize imgFrames only when unlocking. r=mwu
authorSeth Fowler <seth@mozilla.com>
Thu, 18 Sep 2014 19:26:01 -0700
changeset 206125 4b14b05cdb75ff392a01f0adc37a71a402cf0f1d
parent 206124 7b175263c92dc3d8a31ec56d3bd285a5b7e6ac4a
child 206126 35fe397259e6aa27bcc1b76d9ce431c6b90024d8
push id27513
push usercbook@mozilla.com
push dateFri, 19 Sep 2014 12:15:35 +0000
treeherdermozilla-central@dadafedc0760 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmwu
bugs1069652
milestone35.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 1069652 - Optimize imgFrames only when unlocking. r=mwu
image/src/RasterImage.cpp
image/src/imgFrame.cpp
image/src/imgFrame.h
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -247,17 +247,18 @@ public:
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     nsRefPtr<RasterImage> image = weakImage.get();
     if (image) {
       image->UnlockImage();
     }
 
-    if (DiscardingEnabled() && dstFrame) {
+    if (dstFrame) {
+      dstFrame->SetOptimizable();
       dstFrame->SetDiscardable();
     }
 
     // Release everything except dstFrame, which we keep around for RasterImage
     // to retrieve.
     srcRef.reset();
     dstRef.reset();
   }
@@ -1417,31 +1418,27 @@ RasterImage::DecodingComplete()
   // We now have one of the qualifications for discarding. Re-evaluate.
   if (CanDiscard()) {
     NS_ABORT_IF_FALSE(!DiscardingActive(),
                       "We shouldn't have been discardable before this");
     rv = DiscardTracker::Reset(&mDiscardTrackerNode);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
-  // If there's only 1 frame, optimize it. Optimizing animated images
+  // If there's only 1 frame, mark it as optimizable. Optimizing animated images
   // is not supported.
   //
   // We don't optimize the frame for multipart images because we reuse
   // the frame.
   if ((GetNumFrames() == 1) && !mMultipart) {
-    // CanForciblyDiscard is used instead of CanForciblyDiscardAndRedecode
-    // because we know decoding is complete at this point and this is not
-    // an animation
     nsRefPtr<imgFrame> firstFrame = mFrameBlender.RawGetFrame(0);
+    firstFrame->SetOptimizable();
     if (DiscardingEnabled() && CanForciblyDiscard()) {
       firstFrame->SetDiscardable();
     }
-    rv = firstFrame->Optimize();
-    NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Double-buffer our frame in the multipart case, since we'll start decoding
   // into the first frame again immediately and this produces severe tearing.
   if (mMultipart) {
     if (GetNumFrames() == 1) {
       mMultipartDecodedFrame = mFrameBlender.SwapFrame(GetCurrentFrameIndex(),
                                                        mMultipartDecodedFrame);
@@ -2602,16 +2599,21 @@ void
 RasterImage::RequestScale(imgFrame* aFrame, nsIntSize aSize)
 {
   // We can't store more than one scaled version of an image right now, so if
   // there's more than one instance of this image, bail.
   if (mLockCount != 1) {
     return;
   }
 
+  // We don't scale frames which aren't fully decoded.
+  if (!aFrame->ImageComplete()) {
+    return;
+  }
+
   // We also can't scale if we can't lock the image data for this frame.
   RawAccessFrameRef frameRef = aFrame->RawAccessRef();
   if (!frameRef) {
     return;
   }
 
   // If we have an outstanding request, signal it to stop (if it can).
   if (mScaleRequest) {
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -117,16 +117,17 @@ imgFrame::imgFrame() :
   mTimeout(100),
   mDisposalMethod(0), /* imgIContainer::kDisposeNotSpecified */
   mLockCount(0),
   mBlendMethod(1), /* imgIContainer::kBlendOver */
   mSinglePixel(false),
   mCompositingFailed(false),
   mNonPremult(false),
   mDiscardable(false),
+  mOptimizable(false),
   mInformedDiscardTracker(false)
 {
   static bool hasCheckedOptimize = false;
   if (!hasCheckedOptimize) {
     if (PR_GetEnv("MOZ_DISABLE_IMAGE_OPTIMIZE")) {
       gDisableOptimize = true;
     }
     hasCheckedOptimize = true;
@@ -293,18 +294,20 @@ imgFrame::InitWithDrawable(gfxDrawable* 
   }
 
   return NS_OK;
 }
 
 nsresult imgFrame::Optimize()
 {
   MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mLockCount == 1,
+             "Should only optimize when holding the lock exclusively");
 
-  if (gDisableOptimize)
+  if (!mOptimizable || gDisableOptimize)
     return NS_OK;
 
   if (mPalettedImageData || mOptSurface || mSinglePixel)
     return NS_OK;
 
   // Don't do single-color opts on non-premult data.
   // Cairo doesn't support non-premult single-colors.
   if (mNonPremult)
@@ -396,16 +399,25 @@ nsresult imgFrame::Optimize()
 #endif
 
   if (mOptSurface) {
     mVBuf = nullptr;
     mVBufPtr = nullptr;
     mImageSurface = nullptr;
   }
 
+#ifdef MOZ_WIDGET_ANDROID
+  // On Android, free mImageSurface unconditionally if we're discardable.
+  // XXX(seth): We'd eventually like to do this on all platforms, but right now
+  // we'd read back from the GPU too much to make it worthwhile.
+  if (mDiscardable) {
+    mImageSurface = nullptr;
+  }
+#endif
+
   return NS_OK;
 }
 
 DrawableFrameRef
 imgFrame::DrawableRef()
 {
   return DrawableFrameRef(this);
 }
@@ -714,52 +726,50 @@ nsresult imgFrame::LockImageData()
   mVBufPtr = mVBuf;
   return NS_OK;
 }
 
 nsresult imgFrame::UnlockImageData()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  NS_ABORT_IF_FALSE(mLockCount != 0, "Unlocking an unlocked image!");
-  if (mLockCount == 0) {
+  MOZ_ASSERT(mLockCount > 0, "Unlocking an unlocked image!");
+  if (mLockCount <= 0) {
     return NS_ERROR_FAILURE;
   }
 
+  // If we're about to become unlocked, we don't need to hold on to our data
+  // surface anymore. (But we don't need to do anything for paletted images,
+  // which don't have surfaces.)
+  if (mLockCount == 1 && !mPalettedImageData) {
+    // Convert the data surface to a GPU surface or a single color if possible.
+    // This will also release mImageSurface if possible.
+    Optimize();
+    
+    // Allow the OS to release our data surface.
+    mVBufPtr = nullptr;
+  }
+
   mLockCount--;
 
-  NS_ABORT_IF_FALSE(mLockCount >= 0, "Unbalanced locks and unlocks");
-  if (mLockCount < 0) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // If we are not the last lock, there's nothing to do.
-  if (mLockCount != 0) {
-    return NS_OK;
-  }
-
-  // Paletted images don't have surfaces, so there's nothing to do.
-  if (mPalettedImageData)
-    return NS_OK;
-
-  mVBufPtr = nullptr;
-  if (mVBuf && mDiscardable) {
-    mImageSurface = nullptr;
-  }
-
   return NS_OK;
 }
 
-void imgFrame::SetDiscardable()
+void
+imgFrame::SetDiscardable()
 {
   MOZ_ASSERT(mLockCount, "Expected to be locked when SetDiscardable is called");
-  // Disabled elsewhere due to the cost of calling GetSourceSurfaceForSurface.
-#ifdef MOZ_WIDGET_ANDROID
   mDiscardable = true;
-#endif
+}
+
+void
+imgFrame::SetOptimizable()
+{
+  MOZ_ASSERT(mLockCount, "Expected to be locked when SetOptimizable is called");
+  mOptimizable = true;
 }
 
 TemporaryRef<SourceSurface>
 imgFrame::GetSurface()
 {
   if (mOptSurface) {
     if (mOptSurface->IsValid())
       return mOptSurface;
--- a/image/src/imgFrame.h
+++ b/image/src/imgFrame.h
@@ -69,18 +69,16 @@ public:
    * case.
    */
   nsresult InitWithDrawable(gfxDrawable* aDrawable,
                             const nsIntSize& aSize,
                             const SurfaceFormat aFormat,
                             GraphicsFilter aFilter,
                             uint32_t aImageFlags);
 
-  nsresult Optimize();
-
   DrawableFrameRef DrawableRef();
   RawAccessFrameRef RawAccessRef();
 
   bool Draw(gfxContext* aContext, const ImageRegion& aRegion,
             const nsIntMargin& aPadding, GraphicsFilter aFilter,
             uint32_t aImageFlags);
 
   nsresult ImageUpdated(const nsIntRect &aUpdateRect);
@@ -113,16 +111,17 @@ public:
 
   bool GetCompositingFailed() const;
   void SetCompositingFailed(bool val);
 
   nsresult LockImageData();
   nsresult UnlockImageData();
 
   void SetDiscardable();
+  void SetOptimizable();
 
   TemporaryRef<SourceSurface> GetSurface();
   TemporaryRef<DrawTarget> GetDrawTarget();
 
   Color
   SinglePixelColor()
   {
     return mSinglePixelColor;
@@ -146,16 +145,18 @@ public:
 
     return ((1 << mPaletteDepth) * sizeof(uint32_t));
   }
 
 private: // methods
 
   ~imgFrame();
 
+  nsresult Optimize();
+
   struct SurfaceWithFormat {
     nsRefPtr<gfxDrawable> mDrawable;
     SurfaceFormat mFormat;
     SurfaceWithFormat() {}
     SurfaceWithFormat(gfxDrawable* aDrawable, SurfaceFormat aFormat)
      : mDrawable(aDrawable), mFormat(aFormat) {}
     bool IsValid() { return !!mDrawable; }
   };
@@ -200,16 +201,17 @@ private: // data
 
   SurfaceFormat mFormat;
   uint8_t      mPaletteDepth;
   int8_t       mBlendMethod;
   bool mSinglePixel;
   bool mCompositingFailed;
   bool mNonPremult;
   bool mDiscardable;
+  bool mOptimizable;
 
   /** Have we called DiscardTracker::InformAllocation()? */
   bool mInformedDiscardTracker;
 
   friend class DrawableFrameRef;
   friend class RawAccessFrameRef;
 };