Bug 1339202 - Decode images to shared surfaces for WebRender. r=tnikkel
authorAndrew Osmond <aosmond@mozilla.com>
Wed, 08 Feb 2017 15:48:59 -0500
changeset 389716 d713355dff6c69f2895b4620d2c1ee68ff3aedf3
parent 389715 70af77d96837b56555fc28068eaaba9bb35def1f
child 389717 80e208e3b651ada2433668b86a6c8e1bacd3f8d2
push id7198
push userjlorenzo@mozilla.com
push dateTue, 18 Apr 2017 12:07:49 +0000
treeherdermozilla-beta@d57aa49c3948 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstnikkel
bugs1339202
milestone54.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 1339202 - Decode images to shared surfaces for WebRender. r=tnikkel
gfx/thebes/gfxPrefs.h
image/Decoder.cpp
image/Decoder.h
image/DecoderFactory.cpp
image/RasterImage.cpp
image/decoders/nsICODecoder.cpp
image/imgFrame.cpp
image/imgFrame.h
modules/libpref/init/all.js
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -436,16 +436,17 @@ private:
 
   DECL_GFX_PREF(Once, "image.cache.size",                      ImageCacheSize, int32_t, 5*1024*1024);
   DECL_GFX_PREF(Once, "image.cache.timeweight",                ImageCacheTimeWeight, int32_t, 500);
   DECL_GFX_PREF(Live, "image.decode-immediately.enabled",      ImageDecodeImmediatelyEnabled, bool, false);
   DECL_GFX_PREF(Live, "image.downscale-during-decode.enabled", ImageDownscaleDuringDecodeEnabled, bool, true);
   DECL_GFX_PREF(Live, "image.infer-src-animation.threshold-ms", ImageInferSrcAnimationThresholdMS, uint32_t, 2000);
   DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time",      ImageMemDecodeBytesAtATime, uint32_t, 200000);
   DECL_GFX_PREF(Live, "image.mem.discardable",                 ImageMemDiscardable, bool, false);
+  DECL_GFX_PREF(Live, "image.mem.shared",                      ImageMemShared, bool, false);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb",    ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.min_expiration_ms", ImageMemSurfaceCacheMinExpirationMS, uint32_t, 60*1000);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor",    ImageMemSurfaceCacheSizeFactor, uint32_t, 64);
   DECL_GFX_PREF(Once, "image.multithreaded_decoding.limit",    ImageMTDecodingLimit, int32_t, -1);
 
   DECL_GFX_PREF(Once, "layers.acceleration.disabled",          LayersAccelerationDisabledDoNotUseDirectly, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps",          LayersDrawFPS, bool, false);
--- a/image/Decoder.cpp
+++ b/image/Decoder.cpp
@@ -59,16 +59,17 @@ Decoder::Decoder(RasterImage* aImage)
   , mMetadataDecode(false)
   , mHaveExplicitOutputSize(false)
   , mInFrame(false)
   , mFinishedNewFrame(false)
   , mReachedTerminalState(false)
   , mDecodeDone(false)
   , mError(false)
   , mShouldReportError(false)
+  , mFinalizeFrames(true)
 { }
 
 Decoder::~Decoder()
 {
   MOZ_ASSERT(mProgress == NoProgress || !mImage,
              "Destroying Decoder without taking all its progress changes");
   MOZ_ASSERT(mInvalidRect.IsEmpty() || !mImage,
              "Destroying Decoder without taking all its invalidations");
@@ -450,17 +451,17 @@ Decoder::PostFrameStop(Opacity aFrameOpa
   MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one");
   MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one");
 
   // Update our state.
   mInFrame = false;
   mFinishedNewFrame = true;
 
   mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout,
-                        aBlendMethod, aBlendRect);
+                        aBlendMethod, aBlendRect, mFinalizeFrames);
 
   mProgress |= FLAG_FRAME_COMPLETE;
 
   mLoopLength += aTimeout;
 
   // If we're not sending partial invalidations, then we send an invalidation
   // here when the first frame is complete.
   if (!ShouldSendPartialInvalidations() && mFrameCount == 1) {
--- a/image/Decoder.h
+++ b/image/Decoder.h
@@ -257,16 +257,20 @@ public:
 
   // Did we discover that the image we're decoding is animated?
   bool HasAnimation() const { return mImageMetadata.HasAnimation(); }
 
   // Error tracking
   bool HasError() const { return mError; }
   bool ShouldReportError() const { return mShouldReportError; }
 
+  // Finalize frames
+  void SetFinalizeFrames(bool aFinalize) { mFinalizeFrames = aFinalize; }
+  bool GetFinalizeFrames() const { return mFinalizeFrames; }
+
   /// Did we finish decoding enough that calling Decode() again would be useless?
   bool GetDecodeDone() const
   {
     return mReachedTerminalState || mDecodeDone ||
            (mMetadataDecode && HasSize()) || HasError();
   }
 
   /// Are we in the middle of a frame right now? Used for assertions only.
@@ -541,14 +545,15 @@ private:
   bool mHaveExplicitOutputSize : 1;
   bool mInFrame : 1;
   bool mFinishedNewFrame : 1;  // True if PostFrameStop() has been called since
                                // the last call to TakeCompleteFrameCount().
   bool mReachedTerminalState : 1;
   bool mDecodeDone : 1;
   bool mError : 1;
   bool mShouldReportError : 1;
+  bool mFinalizeFrames : 1;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_Decoder_h
--- a/image/DecoderFactory.cpp
+++ b/image/DecoderFactory.cpp
@@ -261,16 +261,17 @@ DecoderFactory::CreateDecoderForICOResou
 
   // Initialize the decoder, copying settings from @aICODecoder.
   MOZ_ASSERT(!aICODecoder->IsMetadataDecode());
   decoder->SetMetadataDecode(aICODecoder->IsMetadataDecode());
   decoder->SetIterator(aSourceBuffer->Iterator());
   decoder->SetOutputSize(aICODecoder->OutputSize());
   decoder->SetDecoderFlags(aICODecoder->GetDecoderFlags());
   decoder->SetSurfaceFlags(aICODecoder->GetSurfaceFlags());
+  decoder->SetFinalizeFrames(false);
 
   if (NS_FAILED(decoder->Init())) {
     return nullptr;
   }
 
   return decoder.forget();
 }
 
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -474,25 +474,30 @@ RasterImage::GetFirstFrameDelay()
   MOZ_ASSERT(mAnimationState, "Animated images should have an AnimationState");
   return mAnimationState->FirstFrameTimeout().AsEncodedValueDeprecated();
 }
 
 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
 RasterImage::GetFrame(uint32_t aWhichFrame,
                       uint32_t aFlags)
 {
-  return GetFrameInternal(mSize, aWhichFrame, aFlags).second().forget();
+  return GetFrameAtSize(mSize, aWhichFrame, aFlags);
 }
 
 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
 RasterImage::GetFrameAtSize(const IntSize& aSize,
                             uint32_t aWhichFrame,
                             uint32_t aFlags)
 {
-  return GetFrameInternal(aSize, aWhichFrame, aFlags).second().forget();
+  RefPtr<SourceSurface> surf =
+    GetFrameInternal(aSize, aWhichFrame, aFlags).second().forget();
+  // If we are here, it suggests the image is embedded in a canvas or some
+  // other path besides layers, and we won't need the file handle.
+  MarkSurfaceShared(surf);
+  return surf.forget();
 }
 
 Pair<DrawResult, RefPtr<SourceSurface>>
 RasterImage::GetFrameInternal(const IntSize& aSize,
                               uint32_t aWhichFrame,
                               uint32_t aFlags)
 {
   MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE);
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -588,16 +588,23 @@ nsICODecoder::FinishResource()
 {
   // Make sure the actual size of the resource matches the size in the directory
   // entry. If not, we consider the image corrupt.
   if (mContainedDecoder->HasSize() &&
       mContainedDecoder->Size() != GetRealSize()) {
     return Transition::TerminateFailure();
   }
 
+  // Finalize the frame which we deferred to ensure we could modify the final
+  // result (e.g. to apply the BMP mask).
+  MOZ_ASSERT(!mContainedDecoder->GetFinalizeFrames());
+  if (mCurrentFrame) {
+    mCurrentFrame->FinalizeSurface();
+  }
+
   return Transition::TerminateSuccess();
 }
 
 LexerResult
 nsICODecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
 {
   MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
 
--- a/image/imgFrame.cpp
+++ b/image/imgFrame.cpp
@@ -15,16 +15,17 @@
 #include "gfxPrefs.h"
 #include "gfxUtils.h"
 #include "gfxAlphaRecovery.h"
 
 #include "GeckoProfiler.h"
 #include "MainThreadUtils.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/gfx/Tools.h"
+#include "mozilla/layers/SourceSurfaceSharedData.h"
 #include "mozilla/layers/SourceSurfaceVolatileData.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsMargin.h"
 #include "nsThreadUtils.h"
 
 
 namespace mozilla {
@@ -46,16 +47,22 @@ VolatileSurfaceStride(const IntSize& siz
   return (size.width * BytesPerPixel(format) + 0x3) & ~0x3;
 }
 
 static already_AddRefed<DataSourceSurface>
 CreateLockedSurface(DataSourceSurface *aSurface,
                     const IntSize& size,
                     SurfaceFormat format)
 {
+  // Shared memory is never released until the surface itself is released
+  if (aSurface->GetType() == SurfaceType::DATA_SHARED) {
+    RefPtr<DataSourceSurface> surf(aSurface);
+    return surf.forget();
+  }
+
   DataSourceSurface::ScopedMap* smap =
     new DataSourceSurface::ScopedMap(aSurface, DataSourceSurface::READ_WRITE);
   if (smap->IsMapped()) {
     // The ScopedMap is held by this DataSourceSurface.
     RefPtr<DataSourceSurface> surf =
       Factory::CreateWrappingDataSourceSurface(smap->GetData(),
                                                aSurface->Stride(),
                                                size,
@@ -72,21 +79,27 @@ CreateLockedSurface(DataSourceSurface *a
 }
 
 static already_AddRefed<DataSourceSurface>
 AllocateBufferForImage(const IntSize& size,
                        SurfaceFormat format,
                        bool aIsAnimated = false)
 {
   int32_t stride = VolatileSurfaceStride(size, format);
-  RefPtr<SourceSurfaceVolatileData> newSurf = new SourceSurfaceVolatileData();
-  if (newSurf->Init(size, stride, format)) {
-    return newSurf.forget();
+  if (!aIsAnimated && gfxPrefs::ImageMemShared()) {
+    RefPtr<SourceSurfaceSharedData> newSurf = new SourceSurfaceSharedData();
+    if (newSurf->Init(size, stride, format)) {
+      return newSurf.forget();
+    }
+  } else {
+    RefPtr<SourceSurfaceVolatileData> newSurf= new SourceSurfaceVolatileData();
+    if (newSurf->Init(size, stride, format)) {
+      return newSurf.forget();
+    }
   }
-
   return nullptr;
 }
 
 static bool
 ClearSurface(DataSourceSurface* aSurface, const IntSize& aSize, SurfaceFormat aFormat)
 {
   int32_t stride = aSurface->Stride();
   uint8_t* data = aSurface->GetData();
@@ -103,16 +116,29 @@ ClearSurface(DataSourceSurface* aSurface
     // Otherwise, it's allocated via mmap and refers to a zeroed page and will
     // be COW once it's written to.
     memset(data, 0, stride * aSize.height);
   }
 
   return true;
 }
 
+void
+MarkSurfaceShared(SourceSurface* aSurface)
+{
+  // Depending on what requested the image decoding, the buffer may or may not
+  // end up being shared with another process (e.g. put in a painted layer,
+  // used inside a canvas). If not shared, we should ensure are not keeping the
+  // handle only because we have yet to share it.
+  if (aSurface && aSurface->GetType() == SurfaceType::DATA_SHARED) {
+    auto sharedSurface = static_cast<SourceSurfaceSharedData*>(aSurface);
+    sharedSurface->FinishedSharing();
+  }
+}
+
 // Returns true if an image of aWidth x aHeight is allowed and legal.
 static bool
 AllowedImageSize(int32_t aWidth, int32_t aHeight)
 {
   // reject over-wide or over-tall images
   const int32_t k64KLimit = 0x0000FFFF;
   if (MOZ_UNLIKELY(aWidth > k64KLimit || aHeight > k64KLimit )) {
     NS_WARNING("image too big");
@@ -354,16 +380,18 @@ imgFrame::InitWithDrawable(gfxDrawable* 
     mAborted = true;
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   if (!canUseDataSurface) {
     // We used an offscreen surface, which is an "optimized" surface from
     // imgFrame's perspective.
     mOptSurface = target->Snapshot();
+  } else {
+    FinalizeSurface();
   }
 
   // If we reach this point, we should regard ourselves as complete.
   mDecoded = GetRect();
   mFinished = true;
 
 #ifdef DEBUG
   MonitorAutoLock lock(mMonitor);
@@ -546,16 +574,20 @@ bool imgFrame::Draw(gfxContext* aContext
   SurfaceWithFormat surfaceResult =
     SurfaceForDrawing(doPartialDecode, doTile, region, surf);
 
   if (surfaceResult.IsValid()) {
     gfxUtils::DrawPixelSnapped(aContext, surfaceResult.mDrawable,
                                imageRect.Size(), region, surfaceResult.mFormat,
                                aSamplingFilter, aImageFlags, aOpacity);
   }
+
+  // Image got put into a painted layer, it will not be shared with another
+  // process.
+  MarkSurfaceShared(surf);
   return true;
 }
 
 nsresult
 imgFrame::ImageUpdated(const nsIntRect& aUpdateRect)
 {
   MonitorAutoLock lock(mMonitor);
   return ImageUpdatedInternal(aUpdateRect);
@@ -576,26 +608,32 @@ imgFrame::ImageUpdatedInternal(const nsI
 }
 
 void
 imgFrame::Finish(Opacity aFrameOpacity /* = Opacity::SOME_TRANSPARENCY */,
                  DisposalMethod aDisposalMethod /* = DisposalMethod::KEEP */,
                  FrameTimeout aTimeout
                    /* = FrameTimeout::FromRawMilliseconds(0) */,
                  BlendMethod aBlendMethod /* = BlendMethod::OVER */,
-                 const Maybe<IntRect>& aBlendRect /* = Nothing() */)
+                 const Maybe<IntRect>& aBlendRect /* = Nothing() */,
+                 bool aFinalize /* = true */)
 {
   MonitorAutoLock lock(mMonitor);
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
 
   mDisposalMethod = aDisposalMethod;
   mTimeout = aTimeout;
   mBlendMethod = aBlendMethod;
   mBlendRect = aBlendRect;
   ImageUpdatedInternal(GetRect());
+
+  if (aFinalize) {
+    FinalizeSurfaceInternal();
+  }
+
   mFinished = true;
 
   // The image is now complete, wake up anyone who's waiting.
   mMonitor.NotifyAll();
 }
 
 uint32_t
 imgFrame::GetImageBytesPerRow() const
@@ -628,16 +666,19 @@ imgFrame::GetImageData(uint8_t** aData, 
 
 void
 imgFrame::GetImageDataInternal(uint8_t** aData, uint32_t* aLength) const
 {
   mMonitor.AssertCurrentThreadOwns();
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
 
   if (mLockedSurface) {
+    // TODO: This is okay for now because we only realloc shared surfaces on
+    // the main thread after decoding has finished, but if animations want to
+    // read frame data off the main thread, we will need to reconsider this.
     *aData = mLockedSurface->GetData();
     MOZ_ASSERT(*aData,
       "mLockedSurface is non-null, but GetData is null in GetImageData");
   } else if (mPalettedImageData) {
     *aData = mPalettedImageData + PaletteDataLength();
     MOZ_ASSERT(*aData,
       "mPalettedImageData is non-null, but result is null in GetImageData");
   } else {
@@ -748,16 +789,37 @@ imgFrame::UnlockImageData()
 void
 imgFrame::SetOptimizable()
 {
   AssertImageDataLocked();
   MonitorAutoLock lock(mMonitor);
   mOptimizable = true;
 }
 
+void
+imgFrame::FinalizeSurface()
+{
+  MonitorAutoLock lock(mMonitor);
+  FinalizeSurfaceInternal();
+}
+
+void
+imgFrame::FinalizeSurfaceInternal()
+{
+  mMonitor.AssertCurrentThreadOwns();
+
+  // Not all images will have mRawSurface to finalize (i.e. paletted images).
+  if (!mRawSurface || mRawSurface->GetType() != SurfaceType::DATA_SHARED) {
+    return;
+  }
+
+  auto sharedSurf = static_cast<SourceSurfaceSharedData*>(mRawSurface.get());
+  sharedSurf->Finalize();
+}
+
 already_AddRefed<SourceSurface>
 imgFrame::GetSourceSurface()
 {
   MonitorAutoLock lock(mMonitor);
   return GetSourceSurfaceInternal();
 }
 
 already_AddRefed<SourceSurface>
--- a/image/imgFrame.h
+++ b/image/imgFrame.h
@@ -274,22 +274,25 @@ public:
    *                         displayed.
    * @param aTimeout         For animation frames, the timeout before the next
    *                         frame is displayed.
    * @param aBlendMethod     For animation frames, a blending method to be used
    *                         when compositing this frame.
    * @param aBlendRect       For animation frames, if present, the subrect in
    *                         which @aBlendMethod applies. Outside of this
    *                         subrect, BlendMethod::OVER is always used.
+   * @param aFinalize        Finalize the underlying surface (e.g. so that it
+   *                         may be marked as read only if possible).
    */
   void Finish(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY,
               DisposalMethod aDisposalMethod = DisposalMethod::KEEP,
               FrameTimeout aTimeout = FrameTimeout::FromRawMilliseconds(0),
               BlendMethod aBlendMethod = BlendMethod::OVER,
-              const Maybe<IntRect>& aBlendRect = Nothing());
+              const Maybe<IntRect>& aBlendRect = Nothing(),
+              bool aFinalize = true);
 
   /**
    * Mark this imgFrame as aborted. This informs the imgFrame that if it isn't
    * completely decoded now, it never will be.
    *
    * You must always call either Finish() or Abort() before releasing the last
    * RawAccessFrameRef pointing to an imgFrame.
    */
@@ -336,16 +339,17 @@ public:
 
   AnimationData GetAnimationData() const;
 
   bool GetCompositingFailed() const;
   void SetCompositingFailed(bool val);
 
   void SetOptimizable();
 
+  void FinalizeSurface();
   already_AddRefed<SourceSurface> GetSourceSurface();
 
   void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, size_t& aHeapSizeOut,
                               size_t& aNonHeapSizeOut) const;
 
 private: // methods
 
   ~imgFrame();
@@ -356,16 +360,17 @@ private: // methods
 
   void AssertImageDataLocked() const;
 
   bool AreAllPixelsWritten() const;
   nsresult ImageUpdatedInternal(const nsIntRect& aUpdateRect);
   void GetImageDataInternal(uint8_t** aData, uint32_t* length) const;
   uint32_t GetImageBytesPerRow() const;
   uint32_t GetImageDataLength() const;
+  void FinalizeSurfaceInternal();
   already_AddRefed<SourceSurface> GetSourceSurfaceInternal();
 
   uint32_t PaletteDataLength() const
   {
     return mPaletteDepth ? (size_t(1) << mPaletteDepth) * sizeof(uint32_t)
                          : 0;
   }
 
@@ -612,12 +617,14 @@ public:
   }
 
 private:
   RawAccessFrameRef(const RawAccessFrameRef& aOther) = delete;
 
   RefPtr<imgFrame> mFrame;
 };
 
+void MarkSurfaceShared(gfx::SourceSurface* aSurface);
+
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_imgFrame_h
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4446,16 +4446,20 @@ pref("image.infer-src-animation.threshol
 //
 // Image memory management prefs
 //
 
 // Discards inactive image frames and re-decodes them on demand from
 // compressed data.
 pref("image.mem.discardable", true);
 
+// Decodes images into shared memory to allow direct use in separate
+// rendering processes.
+pref("image.mem.shared", false);
+
 // Allows image locking of decoded image data in content processes.
 pref("image.mem.allow_locking_in_content_processes", true);
 
 // Chunk size for calls to the image decoders
 pref("image.mem.decode_bytes_at_a_time", 16384);
 
 // Minimum timeout for expiring unused images from the surface cache, in
 // milliseconds. This controls how long we store cached temporary surfaces.