Bug 1444432 - Implement screenshot grabbing in LayerManagerComposite. r?jrmuizel draft
authorMarkus Stange <mstange@themasta.com>
Thu, 12 Apr 2018 15:31:12 -0400
changeset 781551 1d93f11cb34a7fa18eec3a7317d6261385602184
parent 781550 4683c63356a434e5283e82a9b50d0410521ee264
push id106334
push userbmo:mstange@themasta.com
push dateFri, 13 Apr 2018 04:36:02 +0000
reviewersjrmuizel
bugs1444432
milestone61.0a1
Bug 1444432 - Implement screenshot grabbing in LayerManagerComposite. r?jrmuizel MozReview-Commit-ID: 4BNKNwCACUB
gfx/layers/composite/CompositorScreenshotGrabber.cpp
gfx/layers/composite/CompositorScreenshotGrabber.h
gfx/layers/composite/LayerManagerComposite.cpp
gfx/layers/composite/LayerManagerComposite.h
gfx/layers/moz.build
new file mode 100644
--- /dev/null
+++ b/gfx/layers/composite/CompositorScreenshotGrabber.cpp
@@ -0,0 +1,249 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "CompositorScreenshotGrabber.h"
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+
+#include "mozilla/layers/ProfilerScreenshots.h"
+#include "mozilla/gfx/Point.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+using namespace gfx;
+
+namespace layers {
+
+/**
+ * The actual implementation of screenshot grabbing.
+ * The CompositorScreenshotGrabberImpl object is destroyed if the profiler is
+ * disabled and MaybeGrabScreenshot notices it.
+ */
+class CompositorScreenshotGrabberImpl final
+{
+public:
+  explicit CompositorScreenshotGrabberImpl(const IntSize& aBufferSize);
+  ~CompositorScreenshotGrabberImpl();
+
+  void GrabScreenshot(Compositor* aCompositor);
+  void ProcessQueue();
+
+private:
+  struct QueueItem final
+  {
+    mozilla::TimeStamp mTimeStamp;
+    RefPtr<AsyncReadbackBuffer> mScreenshotBuffer;
+    gfx::IntSize mScreenshotSize;
+    gfx::IntSize mWindowSize;
+    uintptr_t mWindowIdentifier;
+  };
+
+  RefPtr<CompositingRenderTarget>
+  ScaleDownWindowTargetToSize(Compositor* aCompositor,
+                              const gfx::IntSize& aDestSize,
+                              CompositingRenderTarget* aWindowTarget,
+                              size_t aLevel);
+
+  already_AddRefed<AsyncReadbackBuffer> TakeNextBuffer(Compositor* aCompositor);
+  void ReturnBuffer(AsyncReadbackBuffer* aBuffer);
+
+  nsTArray<RefPtr<CompositingRenderTarget>> mTargets;
+  nsTArray<RefPtr<AsyncReadbackBuffer>> mAvailableBuffers;
+  Maybe<QueueItem> mCurrentFrameQueueItem;
+  nsTArray<QueueItem> mQueue;
+  UniquePtr<ProfilerScreenshots> mProfilerScreenshots;
+  const IntSize mBufferSize;
+};
+
+CompositorScreenshotGrabber::CompositorScreenshotGrabber()
+{
+}
+
+CompositorScreenshotGrabber::~CompositorScreenshotGrabber()
+{
+}
+
+void
+CompositorScreenshotGrabber::MaybeGrabScreenshot(Compositor* aCompositor)
+{
+  if (ProfilerScreenshots::IsEnabled()) {
+    if (!mImpl) {
+      mImpl = MakeUnique<CompositorScreenshotGrabberImpl>(ProfilerScreenshots::ScreenshotSize());
+    }
+    mImpl->GrabScreenshot(aCompositor);
+  } else if (mImpl) {
+    Destroy();
+  }
+}
+
+void
+CompositorScreenshotGrabber::MaybeProcessQueue()
+{
+  if (ProfilerScreenshots::IsEnabled()) {
+    if (!mImpl) {
+      mImpl = MakeUnique<CompositorScreenshotGrabberImpl>(ProfilerScreenshots::ScreenshotSize());
+    }
+    mImpl->ProcessQueue();
+  } else if (mImpl) {
+    Destroy();
+  }
+}
+
+void
+CompositorScreenshotGrabber::NotifyEmptyFrame()
+{
+#ifdef MOZ_GECKO_PROFILER
+  profiler_add_marker("NoCompositorScreenshot because nothing changed");
+#endif
+}
+
+void
+CompositorScreenshotGrabber::Destroy()
+{
+  mImpl = nullptr;
+}
+
+CompositorScreenshotGrabberImpl::CompositorScreenshotGrabberImpl(const IntSize& aBufferSize)
+  : mBufferSize(aBufferSize)
+{
+}
+
+CompositorScreenshotGrabberImpl::~CompositorScreenshotGrabberImpl()
+{
+  // Any queue items in mQueue or mCurrentFrameQueueItem will be lost.
+  // That's ok: Either the profiler has stopped and we don't care about these
+  // screenshots, or the window is closing and we don't really need the last
+  // few frames from the window.
+}
+
+// Scale down aWindowTarget into a CompositingRenderTarget of size
+// mBufferSize * (1 << aLevel) and return that CompositingRenderTarget.
+// Don't scale down by more than a factor of 2 with a single scaling operation,
+// because it'll look bad. If higher scales are needed, use another
+// intermediate target by calling this function recursively with aLevel + 1.
+RefPtr<CompositingRenderTarget>
+CompositorScreenshotGrabberImpl::ScaleDownWindowTargetToSize(Compositor* aCompositor,
+                                                             const IntSize& aDestSize,
+                                                             CompositingRenderTarget* aWindowTarget,
+                                                             size_t aLevel)
+{
+  if (aLevel == mTargets.Length()) {
+    mTargets.AppendElement(aCompositor->CreateRenderTarget(
+      IntRect(IntPoint(), mBufferSize * (1 << aLevel)), INIT_MODE_NONE));
+  }
+  MOZ_RELEASE_ASSERT(aLevel < mTargets.Length());
+
+  RefPtr<CompositingRenderTarget> sourceTarget = aWindowTarget;
+  IntSize sourceSize = aWindowTarget->GetSize();
+  if (aWindowTarget->GetSize().width > aDestSize.width * 2) {
+    sourceSize = aDestSize * 2;
+    sourceTarget = ScaleDownWindowTargetToSize(aCompositor, sourceSize,
+                                               aWindowTarget, aLevel + 1);
+  }
+
+  if (sourceTarget) {
+    aCompositor->SetRenderTarget(mTargets[aLevel]);
+    if (aCompositor->BlitRenderTarget(sourceTarget, sourceSize, aDestSize)) {
+      return mTargets[aLevel];
+    }
+  }
+  return nullptr;
+}
+
+void
+CompositorScreenshotGrabberImpl::GrabScreenshot(Compositor* aCompositor)
+{
+  RefPtr<CompositingRenderTarget> previousTarget =
+    aCompositor->GetCurrentRenderTarget();
+
+  RefPtr<CompositingRenderTarget> windowTarget =
+    aCompositor->GetWindowRenderTarget();
+
+  if (!windowTarget) {
+    PROFILER_ADD_MARKER("NoCompositorScreenshot because of unsupported compositor configuration");
+    return;
+  }
+
+  Size windowSize(windowTarget->GetSize());
+  float scale = std::min(mBufferSize.width / windowSize.width,
+                         mBufferSize.height / windowSize.height);
+  IntSize scaledSize = IntSize::Round(windowSize * scale);
+  RefPtr<CompositingRenderTarget> scaledTarget =
+    ScaleDownWindowTargetToSize(aCompositor, scaledSize, windowTarget, 0);
+
+  // Restore the old render target.
+  aCompositor->SetRenderTarget(previousTarget);
+
+  if (!scaledTarget) {
+    PROFILER_ADD_MARKER("NoCompositorScreenshot because ScaleDownWindowTargetToSize failed");
+    return;
+  }
+
+  RefPtr<AsyncReadbackBuffer> buffer = TakeNextBuffer(aCompositor);
+  if (!buffer) {
+    PROFILER_ADD_MARKER("NoCompositorScreenshot because AsyncReadbackBuffer creation failed");
+    return;
+  }
+
+  aCompositor->ReadbackRenderTarget(scaledTarget, buffer);
+
+  // This QueueItem will be added to the queue at the end of the next call to
+  // ProcessQueue(). This ensures that the buffer isn't mapped into main memory
+  // until the next frame. If we did it in this frame, we'd block on the GPU.
+  mCurrentFrameQueueItem = Some(QueueItem{
+    TimeStamp::Now(), buffer.forget(), scaledSize, windowTarget->GetSize(),
+    reinterpret_cast<uintptr_t>(static_cast<void*>(this))
+  });
+}
+
+already_AddRefed<AsyncReadbackBuffer>
+CompositorScreenshotGrabberImpl::TakeNextBuffer(Compositor* aCompositor)
+{
+  if (!mAvailableBuffers.IsEmpty()) {
+    RefPtr<AsyncReadbackBuffer> buffer = mAvailableBuffers[0];
+    mAvailableBuffers.RemoveElementAt(0);
+    return buffer.forget();
+  }
+  return aCompositor->CreateAsyncReadbackBuffer(mBufferSize);
+}
+
+void
+CompositorScreenshotGrabberImpl::ReturnBuffer(AsyncReadbackBuffer* aBuffer)
+{
+  mAvailableBuffers.AppendElement(aBuffer);
+}
+
+void
+CompositorScreenshotGrabberImpl::ProcessQueue()
+{
+  if (!mQueue.IsEmpty()) {
+    if (!mProfilerScreenshots) {
+      mProfilerScreenshots = MakeUnique<ProfilerScreenshots>();
+    }
+    for (const auto& item : mQueue) {
+      mProfilerScreenshots->SubmitScreenshot(
+        item.mWindowIdentifier, item.mWindowSize,
+        item.mScreenshotSize, item.mTimeStamp,
+        [&item](DataSourceSurface* aTargetSurface) {
+          return item.mScreenshotBuffer->MapAndCopyInto(aTargetSurface,
+                                                        item.mScreenshotSize);
+        });
+      ReturnBuffer(item.mScreenshotBuffer);
+    }
+  }
+  mQueue.Clear();
+
+  if (mCurrentFrameQueueItem) {
+    mQueue.AppendElement(Move(*mCurrentFrameQueueItem));
+    mCurrentFrameQueueItem = Nothing();
+  }
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/composite/CompositorScreenshotGrabber.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_layers_CompositorScreenshotGrabber_h
+#define mozilla_layers_CompositorScreenshotGrabber_h
+
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace layers {
+
+class CompositorScreenshotGrabberImpl;
+
+/**
+ * Used by LayerManagerComposite to grab snapshots from the compositor and
+ * submit them to the Gecko profiler.
+ * Doesn't do any work if the profiler is not running or the "screenshots"
+ * feature is not enabled.
+ * Screenshots are scaled down to fit within a fixed size, and read back to
+ * main memory using async readback. Scaling is done in multiple scale-by-0.5x
+ * steps using CompositingRenderTargets and Compositor::BlitFromRenderTarget,
+ * and readback is done using AsyncReadbackBuffers.
+ */
+class CompositorScreenshotGrabber final
+{
+public:
+  CompositorScreenshotGrabber();
+  ~CompositorScreenshotGrabber();
+
+  // Scale the contents of aCompositor's current render target into an
+  // approapriately sized CompositingRenderTarget and read its contents into an
+  // AsyncReadbackBuffer. The AsyncReadbackBuffer is not mapped into main
+  // memory until the second call to MaybeProcessQueue() after this call to
+  // MaybeGrabScreenshot().
+  void MaybeGrabScreenshot(Compositor* aCompositor);
+
+  // Map the contents of any outstanding AsyncReadbackBuffers from previous
+  // composites into main memory and submit each screenshot to the profiler.
+  void MaybeProcessQueue();
+
+  // Insert a special profiler marker for a composite that didn't do any actual
+  // compositing, so that the profiler knows why no screenshot was taken for
+  // this frame.
+  void NotifyEmptyFrame();
+
+  // Destroy all Compositor-related resources that this class is holding on to.
+  void Destroy();
+
+private:
+  // non-null while ProfilerScreenshots::IsEnabled() returns true
+  UniquePtr<CompositorScreenshotGrabberImpl> mImpl;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_CompositorScreenshotGrabber_h
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -174,16 +174,17 @@ LayerManagerComposite::Destroy()
   if (!mDestroyed) {
     mCompositor->GetWidget()->CleanupWindowEffects();
     if (mRoot) {
       RootLayer()->Destroy();
     }
     mCompositor->CancelFrame();
     mRoot = nullptr;
     mClonedLayerTreeProperties = nullptr;
+    mProfilerScreenshotGrabber.Destroy();
     mDestroyed = true;
 
 #ifdef USE_SKIA
     mPaintCounter = nullptr;
 #endif
   }
 }
 
@@ -918,16 +919,17 @@ LayerManagerComposite::Render(const nsIn
     clipRect = ParentLayerIntRect(rect.X(), rect.Y(), rect.Width(), rect.Height());
   }
 #if defined(MOZ_WIDGET_ANDROID)
   ScreenCoord offset = GetContentShiftForToolbar();
   ScopedCompositorRenderOffset scopedOffset(mCompositor->AsCompositorOGL(), ScreenPoint(0.0f, offset));
 #endif
 
   if (actualBounds.IsEmpty()) {
+    mProfilerScreenshotGrabber.NotifyEmptyFrame();
     mCompositor->GetWidget()->PostRender(&widgetContext);
     return;
   }
 
   // Allow widget to render a custom background.
   mCompositor->GetWidget()->DrawWindowUnderlay(
     &widgetContext, LayoutDeviceIntRect::FromUnknownRect(actualBounds));
 
@@ -968,16 +970,18 @@ LayerManagerComposite::Render(const nsIn
     PopGroupForLayerEffects(previousTarget, clipRect.ToUnknownRect(),
                             grayscaleVal, invertVal, contrastVal);
   }
 
   // Allow widget to render a custom foreground.
   mCompositor->GetWidget()->DrawWindowOverlay(
     &widgetContext, LayoutDeviceIntRect::FromUnknownRect(actualBounds));
 
+  mProfilerScreenshotGrabber.MaybeGrabScreenshot(mCompositor);
+
   mCompositor->NormalDrawingDone();
 
 #if defined(MOZ_WIDGET_ANDROID)
   // Depending on the content shift the toolbar may be rendered on top of
   // some of the content so it must be rendered after the content.
   RenderToolbar();
   HandlePixelsTarget();
 #endif // defined(MOZ_WIDGET_ANDROID)
@@ -991,16 +995,18 @@ LayerManagerComposite::Render(const nsIn
     mCompositor->EndFrame();
 
     // Call after EndFrame()
     mCompositor->SetDispAcquireFence(mRoot);
   }
 
   mCompositor->GetWidget()->PostRender(&widgetContext);
 
+  mProfilerScreenshotGrabber.MaybeProcessQueue();
+
   RecordFrame();
 }
 
 #if defined(MOZ_WIDGET_ANDROID)
 class ScopedCompositorProjMatrix {
 public:
   ScopedCompositorProjMatrix(CompositorOGL* aCompositor, const Matrix4x4& aProjMatrix):
     mCompositor(aCompositor),
--- a/gfx/layers/composite/LayerManagerComposite.h
+++ b/gfx/layers/composite/LayerManagerComposite.h
@@ -30,16 +30,17 @@
 #include "mozilla/RefPtr.h"                   // for nsRefPtr
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsDebug.h"                    // for NS_ASSERTION
 #include "nsISupportsImpl.h"            // for Layer::AddRef, etc
 #include "nsRect.h"                     // for mozilla::gfx::IntRect
 #include "nsRegion.h"                   // for nsIntRegion
 #include "nscore.h"                     // for nsAString, etc
 #include "LayerTreeInvalidation.h"
+#include "mozilla/layers/CompositorScreenshotGrabber.h"
 
 class gfxContext;
 
 #ifdef XP_WIN
 #include <windows.h>
 #endif
 
 namespace mozilla {
@@ -440,17 +441,16 @@ private:
    */
   void InvalidateDebugOverlay(nsIntRegion& aInvalidRegion, const gfx::IntRect& aBounds);
 
   /**
    * Render debug overlays such as the FPS/FrameCounter above the frame.
    */
   void RenderDebugOverlay(const gfx::IntRect& aBounds);
 
-
   RefPtr<CompositingRenderTarget> PushGroupForLayerEffects();
   void PopGroupForLayerEffects(RefPtr<CompositingRenderTarget> aPreviousTarget,
                                gfx::IntRect aClipRect,
                                bool aGrayscaleEffect,
                                bool aInvertEffect,
                                float aContrastEffect);
 
   bool mUnusedApzTransformWarning;
@@ -465,16 +465,17 @@ private:
   gfx::IntRect mTargetBounds;
 
   nsIntRegion mInvalidRegion;
 
   bool mInTransaction;
   bool mIsCompositorReady;
 
   RefPtr<CompositingRenderTarget> mTwoPassTmpTarget;
+  CompositorScreenshotGrabber mProfilerScreenshotGrabber;
   RefPtr<TextRenderer> mTextRenderer;
 
 #ifdef USE_SKIA
   /**
    * Render paint and composite times above the frame.
    */
   void DrawPaintTimes(Compositor* aCompositor);
   RefPtr<PaintCounter> mPaintCounter;
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -136,16 +136,17 @@ EXPORTS.mozilla.layers += [
     'client/TextureClient.h',
     'client/TextureClientPool.h',
     'client/TextureClientRecycleAllocator.h',
     'client/TextureClientSharedSurface.h',
     'client/TiledContentClient.h',
     'composite/AsyncCompositionManager.h',
     'composite/CanvasLayerComposite.h',
     'composite/ColorLayerComposite.h',
+    'composite/CompositorScreenshotGrabber.h',
     'composite/ContainerLayerComposite.h',
     'composite/ContentHost.h',
     'composite/Diagnostics.h',
     'composite/FPSCounter.h',
     'composite/FrameUniformityData.h',
     'composite/GPUVideoTextureHost.h',
     'composite/ImageComposite.h',
     'composite/ImageHost.h',
@@ -369,16 +370,17 @@ UNIFIED_SOURCES += [
     'client/TextureClientPool.cpp',
     'client/TextureClientRecycleAllocator.cpp',
     'client/TextureClientSharedSurface.cpp',
     'client/TiledContentClient.cpp',
     'composite/AsyncCompositionManager.cpp',
     'composite/CanvasLayerComposite.cpp',
     'composite/ColorLayerComposite.cpp',
     'composite/CompositableHost.cpp',
+    'composite/CompositorScreenshotGrabber.cpp',
     'composite/ContainerLayerComposite.cpp',
     'composite/ContentHost.cpp',
     'composite/Diagnostics.cpp',
     'composite/FPSCounter.cpp',
     'composite/FrameUniformityData.cpp',
     'composite/GPUVideoTextureHost.cpp',
     'composite/ImageComposite.cpp',
     'composite/ImageHost.cpp',