Bug 1525320: Add config prefs that let us tell media and animated images to paint only the first frame r=jya
authorWill Hawkins <whawkins@mozilla.com>
Wed, 13 Mar 2019 02:48:38 +0000
changeset 521817 80d51d772560
parent 521816 2f6199beeb71
child 521818 f73f629e8cca
push id10867
push userdvarga@mozilla.com
push dateThu, 14 Mar 2019 15:20:45 +0000
treeherdermozilla-beta@abad13547875 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjya
bugs1525320
milestone67.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 1525320: Add config prefs that let us tell media and animated images to paint only the first frame r=jya Differential Revision: https://phabricator.services.mozilla.com/D21613
dom/media/mediasink/VideoSink.cpp
dom/media/mediasink/VideoSink.h
image/imgFrame.cpp
image/imgFrame.h
modules/libpref/init/StaticPrefList.h
--- a/dom/media/mediasink/VideoSink.cpp
+++ b/dom/media/mediasink/VideoSink.cpp
@@ -32,16 +32,27 @@ extern LazyLogModule gMediaDecoderLog;
   MOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, (FMT(x, ##__VA_ARGS__)))
 
 using namespace mozilla::layers;
 
 // Minimum update frequency is 1/120th of a second, i.e. half the
 // duration of a 60-fps frame.
 static const int64_t MIN_UPDATE_INTERVAL_US = 1000000 / (60 * 2);
 
+static void SetImageToGreenPixel(PlanarYCbCrImage* aImage) {
+  static uint8_t greenPixel[] = {0x00, 0x00, 0x00};
+  PlanarYCbCrData data;
+  data.mYChannel = greenPixel;
+  data.mCbChannel = greenPixel + 1;
+  data.mCrChannel = greenPixel + 2;
+  data.mYStride = data.mCbCrStride = 1;
+  data.mPicSize = data.mYSize = data.mCbCrSize = gfx::IntSize(1, 1);
+  aImage->CopyData(data);
+}
+
 VideoSink::VideoSink(AbstractThread* aThread, MediaSink* aAudioSink,
                      MediaQueue<VideoData>& aVideoQueue,
                      VideoFrameContainer* aContainer,
                      FrameStatistics& aFrameStats,
                      uint32_t aVQueueSentToCompositerSize)
     : mOwnerThread(aThread),
       mAudioSink(aAudioSink),
       mVideoQueue(aVideoQueue),
@@ -54,19 +65,24 @@ VideoSink::VideoSink(AbstractThread* aTh
       mHasVideo(false),
       mUpdateScheduler(aThread),
       mVideoQueueSendToCompositorSize(aVQueueSentToCompositerSize),
       mMinVideoQueueSize(StaticPrefs::MediaRuinAvSyncEnabled() ? 1 : 0)
 #ifdef XP_WIN
       ,
       mHiResTimersRequested(false)
 #endif
-
 {
   MOZ_ASSERT(mAudioSink, "AudioSink should exist.");
+
+  if (StaticPrefs::browser_measurement_render_anims_and_video_solid() &&
+      mContainer) {
+    InitializeBlankImage();
+    MOZ_ASSERT(mBlankImage, "Blank image should exist.");
+  }
 }
 
 VideoSink::~VideoSink() {
 #ifdef XP_WIN
   MOZ_ASSERT(!mHiResTimersRequested);
 #endif
 }
 
@@ -300,16 +316,19 @@ void VideoSink::Redraw(const VideoInfo& 
   if (!aInfo.IsValid() || !mContainer) {
     return;
   }
 
   auto now = TimeStamp::Now();
 
   RefPtr<VideoData> video = VideoQueue().PeekFront();
   if (video) {
+    if (mBlankImage) {
+      video->mImage = mBlankImage;
+    }
     video->MarkSentToCompositor();
     mContainer->SetCurrentFrame(video->mDisplay, video->mImage, now);
     if (mSecondaryContainer) {
       mSecondaryContainer->SetCurrentFrame(video->mDisplay, video->mImage, now);
     }
     return;
   }
 
@@ -419,16 +438,19 @@ void VideoSink::RenderVideoFrames(int32_
         continue;
       }
       lastFrameTime = t;
     }
 
     ImageContainer::NonOwningImage* img = images.AppendElement();
     img->mTimeStamp = t;
     img->mImage = frame->mImage;
+    if (mBlankImage) {
+      img->mImage = mBlankImage;
+    }
     img->mFrameID = frame->mFrameID;
     img->mProducerID = mProducerID;
 
     VSINK_LOG_V("playing video frame %" PRId64 " (id=%x) (vq-queued=%zu)",
                 frame->mTime.ToMicroseconds(), frame->mFrameID,
                 VideoQueue().GetSize());
   }
 
@@ -570,9 +592,17 @@ nsCString VideoSink::GetDebugInfo() {
       "mVideoSinkEndRequest.Exists()=%d mEndPromiseHolder.IsEmpty()=%d",
       IsStarted(), IsPlaying(), VideoQueue().IsFinished(),
       VideoQueue().GetSize(), mVideoFrameEndTime.ToMicroseconds(), mHasVideo,
       mVideoSinkEndRequest.Exists(), mEndPromiseHolder.IsEmpty());
   AppendStringIfNotEmpty(str, mAudioSink->GetDebugInfo());
   return std::move(str);
 }
 
+bool VideoSink::InitializeBlankImage() {
+  mBlankImage = mContainer->GetImageContainer()->CreatePlanarYCbCrImage();
+  if (mBlankImage == nullptr) {
+    return false;
+  }
+  SetImageToGreenPixel(mBlankImage->AsPlanarYCbCrImage());
+  return true;
+}
 }  // namespace mozilla
--- a/dom/media/mediasink/VideoSink.h
+++ b/dom/media/mediasink/VideoSink.h
@@ -157,13 +157,16 @@ class VideoSink : public MediaSink {
 #ifdef XP_WIN
   // Whether we've called timeBeginPeriod(1) to request high resolution
   // timers. We request high resolution timers when playback starts, and
   // turn them off when playback is paused. Enabling high resolution
   // timers can cause higher CPU usage and battery drain on Windows 7,
   // but reduces our frame drop rate.
   bool mHiResTimersRequested;
 #endif
+
+  RefPtr<layers::Image> mBlankImage;
+  bool InitializeBlankImage();
 };
 
 }  // namespace mozilla
 
 #endif
--- a/image/imgFrame.cpp
+++ b/image/imgFrame.cpp
@@ -116,16 +116,49 @@ static already_AddRefed<DataSourceSurfac
     RefPtr<SourceSurfaceVolatileData> newSurf = new SourceSurfaceVolatileData();
     if (newSurf->Init(size, stride, format)) {
       return newSurf.forget();
     }
   }
   return nullptr;
 }
 
+static bool GreenSurface(DataSourceSurface* aSurface, const IntSize& aSize,
+                         SurfaceFormat aFormat) {
+  int32_t stride = aSurface->Stride();
+  uint32_t* surfaceData = reinterpret_cast<uint32_t*>(aSurface->GetData());
+  uint32_t surfaceDataLength = (stride * aSize.height) / sizeof(uint32_t);
+
+  // Start by assuming that GG is in the second byte and
+  // AA is in the final byte -- the most common case.
+  uint32_t color = mozilla::NativeEndian::swapFromBigEndian(0x00FF00FF);
+
+  // We are only going to handle this type of test under
+  // certain circumstances.
+  MOZ_ASSERT(surfaceData);
+  MOZ_ASSERT(aFormat == SurfaceFormat::B8G8R8A8 ||
+             aFormat == SurfaceFormat::B8G8R8X8 ||
+             aFormat == SurfaceFormat::R8G8B8A8 ||
+             aFormat == SurfaceFormat::R8G8B8X8 ||
+             aFormat == SurfaceFormat::A8R8G8B8 ||
+             aFormat == SurfaceFormat::X8R8G8B8);
+  MOZ_ASSERT((stride * aSize.height) % sizeof(uint32_t));
+
+  if (aFormat == SurfaceFormat::A8R8G8B8 ||
+      aFormat == SurfaceFormat::X8R8G8B8) {
+    color = mozilla::NativeEndian::swapFromBigEndian(0xFF00FF00);
+  }
+
+  for (uint32_t i = 0; i < surfaceDataLength; i++) {
+    surfaceData[i] = color;
+  }
+
+  return true;
+}
+
 static bool ClearSurface(DataSourceSurface* aSurface, const IntSize& aSize,
                          SurfaceFormat aFormat) {
   int32_t stride = aSurface->Stride();
   uint8_t* data = aSurface->GetData();
   MOZ_ASSERT(data);
 
   if (aFormat == SurfaceFormat::B8G8R8X8) {
     // Skia doesn't support RGBX surfaces, so ensure the alpha value is set
@@ -274,29 +307,56 @@ nsresult imgFrame::InitForDecoder(const 
     bool postFirstFrame = aAnimParams && aAnimParams->mFrameNum > 0;
     mRawSurface = AllocateBufferForImage(mFrameRect.Size(), mFormat,
                                          postFirstFrame, mIsFullFrame);
     if (!mRawSurface) {
       mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
+    if (StaticPrefs::browser_measurement_render_anims_and_video_solid() &&
+        aAnimParams) {
+      mBlankRawSurface = AllocateBufferForImage(mFrameRect.Size(), mFormat);
+      if (!mBlankRawSurface) {
+        mAborted = true;
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
+    }
+
     mLockedSurface =
         CreateLockedSurface(mRawSurface, mFrameRect.Size(), mFormat);
     if (!mLockedSurface) {
       NS_WARNING("Failed to create LockedSurface");
       mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
+    if (mBlankRawSurface) {
+      mBlankLockedSurface =
+          CreateLockedSurface(mBlankRawSurface, mFrameRect.Size(), mFormat);
+      if (!mBlankLockedSurface) {
+        NS_WARNING("Failed to create BlankLockedSurface");
+        mAborted = true;
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
+    }
+
     if (!ClearSurface(mRawSurface, mFrameRect.Size(), mFormat)) {
       NS_WARNING("Could not clear allocated buffer");
       mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
+
+    if (mBlankRawSurface) {
+      if (!GreenSurface(mBlankRawSurface, mFrameRect.Size(), mFormat)) {
+        NS_WARNING("Could not clear allocated blank buffer");
+        mAborted = true;
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
+    }
   }
 
   return NS_OK;
 }
 
 nsresult imgFrame::InitForDecoderRecycle(const AnimationParams& aAnimParams) {
   // We want to recycle this frame, but there is no guarantee that consumers are
   // done with it in a timely manner. Let's ensure they are done with it first.
@@ -899,16 +959,33 @@ already_AddRefed<SourceSurface> imgFrame
     if (mOptSurface->IsValid()) {
       RefPtr<SourceSurface> surf(mOptSurface);
       return surf.forget();
     } else {
       mOptSurface = nullptr;
     }
   }
 
+  if (mBlankLockedSurface) {
+    // We are going to return the blank surface because of the flags.
+    // We are including comments here that are copied from below
+    // just so that we are on the same page!
+
+    // We don't need to create recycling wrapper for some callers because they
+    // promise to release the surface immediately after.
+    if (!aTemporary && mShouldRecycle) {
+      RefPtr<SourceSurface> surf =
+          new RecyclingSourceSurface(this, mBlankLockedSurface);
+      return surf.forget();
+    }
+
+    RefPtr<SourceSurface> surf(mBlankLockedSurface);
+    return surf.forget();
+  }
+
   if (mLockedSurface) {
     // We don't need to create recycling wrapper for some callers because they
     // promise to release the surface immediately after.
     if (!aTemporary && mShouldRecycle) {
       RefPtr<SourceSurface> surf =
           new RecyclingSourceSurface(this, mLockedSurface);
       return surf.forget();
     }
--- a/image/imgFrame.h
+++ b/image/imgFrame.h
@@ -295,22 +295,24 @@ class imgFrame {
   mutable Monitor mMonitor;
 
   /**
    * Surface which contains either a weak or a strong reference to its
    * underlying data buffer. If it is a weak reference, and there are no strong
    * references, the buffer may be released due to events such as low memory.
    */
   RefPtr<DataSourceSurface> mRawSurface;
+  RefPtr<DataSourceSurface> mBlankRawSurface;
 
   /**
    * Refers to the same data as mRawSurface, but when set, it guarantees that
    * we hold a strong reference to the underlying data buffer.
    */
   RefPtr<DataSourceSurface> mLockedSurface;
+  RefPtr<DataSourceSurface> mBlankLockedSurface;
 
   /**
    * Optimized copy of mRawSurface for the DrawTarget that will render it. This
    * is unused if the DrawTarget is able to render DataSourceSurface buffers
    * directly.
    */
   RefPtr<SourceSurface> mOptSurface;
 
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -422,16 +422,23 @@ VARCACHE_PREF(
 
 // Enable Performance Observer API
 VARCACHE_PREF(
   "dom.enable_performance_observer",
    dom_enable_performance_observer,
   RelaxedAtomicBool, true
 )
 
+// Render animations and videos as a solid color
+VARCACHE_PREF(
+  "browser.measurement.render_anims_and_video_solid",
+  browser_measurement_render_anims_and_video_solid,
+  RelaxedAtomicBool, false
+)
+
 // Enable passing the "storage" option to indexedDB.open.
 VARCACHE_PREF(
   "dom.indexedDB.storageOption.enabled",
    dom_indexedDB_storageOption_enabled,
   RelaxedAtomicBool, false
 )
 
 #ifdef JS_BUILD_BINAST