Bug 1444430 - Add a ProfilerScreenshots class which can be used to submit DataSourceSurfaces to the profiler. r?jrmuizel draft
authorMarkus Stange <mstange@themasta.com>
Mon, 26 Mar 2018 23:08:33 -0400
changeset 781545 1a82f74ff068a061ae8dc7c948e949afcab492fb
parent 781544 15b8d2e91339d75a1352989615a8f96324d3bdf6
child 781546 1f2ae4e8b9f4c49eda26973c03a4dcb8bde93e09
push id106333
push userbmo:mstange@themasta.com
push dateFri, 13 Apr 2018 04:35:01 +0000
reviewersjrmuizel
bugs1444430
milestone61.0a1
Bug 1444430 - Add a ProfilerScreenshots class which can be used to submit DataSourceSurfaces to the profiler. r?jrmuizel ProfilerScreenshots encodes these surfaces to JPG data URLs, and submits them as profiler markers. The encoding is done on a separate thread. MozReview-Commit-ID: 7CKDBqUsLny
gfx/layers/ProfilerScreenshots.cpp
gfx/layers/ProfilerScreenshots.h
gfx/layers/moz.build
tools/profiler/core/ProfilerMarkerPayload.cpp
tools/profiler/public/ProfilerMarkerPayload.h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ProfilerScreenshots.cpp
@@ -0,0 +1,142 @@
+/* -*- 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 "mozilla/layers/ProfilerScreenshots.h"
+
+#include "mozilla/TimeStamp.h"
+
+#include "GeckoProfiler.h"
+#include "gfxUtils.h"
+#include "nsThreadUtils.h"
+#ifdef MOZ_GECKO_PROFILER
+#include "ProfilerMarkerPayload.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::layers;
+
+ProfilerScreenshots::ProfilerScreenshots()
+  : mMutex("ProfilerScreenshots::mMutex")
+  , mLiveSurfaceCount(0)
+{
+}
+
+ProfilerScreenshots::~ProfilerScreenshots()
+{
+  if (mThread) {
+    mThread->Shutdown();
+    mThread = nullptr;
+  }
+}
+
+/* static */ bool
+ProfilerScreenshots::IsEnabled()
+{
+#ifdef MOZ_GECKO_PROFILER
+  return profiler_feature_active(ProfilerFeature::Screenshots);
+#else
+  return false;
+#endif
+}
+
+void
+ProfilerScreenshots::SubmitScreenshot(uintptr_t aWindowIdentifier,
+                                      const gfx::IntSize& aOriginalSize,
+                                      const IntSize& aScaledSize,
+                                      const TimeStamp& aTimeStamp,
+                                      const std::function<bool(DataSourceSurface*)>& aPopulateSurface)
+{
+#ifdef MOZ_GECKO_PROFILER
+  RefPtr<DataSourceSurface> backingSurface = TakeNextSurface();
+  if (!backingSurface) {
+    return;
+  }
+
+  MOZ_RELEASE_ASSERT(aScaledSize <= backingSurface->GetSize());
+
+  bool succeeded = aPopulateSurface(backingSurface);
+
+  if (!succeeded) {
+    PROFILER_ADD_MARKER("NoCompositorScreenshot because aPopulateSurface callback failed");
+    ReturnSurface(backingSurface);
+    return;
+  }
+
+  if (!mThread) {
+    nsresult rv =
+      NS_NewNamedThread("ProfScreenshot", getter_AddRefs(mThread));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      PROFILER_ADD_MARKER("NoCompositorScreenshot because ProfilerScreenshots thread creation failed");
+      ReturnSurface(backingSurface);
+      return;
+    }
+  }
+
+  int sourceThread = profiler_current_thread_id();
+  uintptr_t windowIdentifier = aWindowIdentifier;
+  IntSize originalSize = aOriginalSize;
+  IntSize scaledSize = aScaledSize;
+  TimeStamp timeStamp = aTimeStamp;
+
+  mThread->Dispatch(
+    NS_NewRunnableFunction("ProfilerScreenshots::SubmitScreenshot",
+                           [this, backingSurface, sourceThread, windowIdentifier,
+                            originalSize, scaledSize, timeStamp]() {
+    // Create a new surface that wraps backingSurface's data but has the correct
+    // size.
+    {
+      DataSourceSurface::ScopedMap scopedMap(backingSurface, DataSourceSurface::READ);
+      RefPtr<DataSourceSurface> surf =
+        Factory::CreateWrappingDataSourceSurface(
+          scopedMap.GetData(), scopedMap.GetStride(), scaledSize, SurfaceFormat::B8G8R8A8);
+
+      // Encode surf to a JPEG data URL.
+      nsCString dataURL;
+      nsresult rv =
+        gfxUtils::EncodeSourceSurface(surf, NS_LITERAL_CSTRING("image/jpeg"),
+                                      NS_LITERAL_STRING("quality=85"),
+                                      gfxUtils::eDataURIEncode,
+                                      nullptr, &dataURL);
+      if (NS_SUCCEEDED(rv)) {
+        // Add a marker with the data URL.
+        profiler_add_marker_for_thread(
+          sourceThread,
+          "CompositorScreenshot",
+          MakeUnique<ScreenshotPayload>(timeStamp, Move(dataURL),
+                                        originalSize, windowIdentifier));
+      }
+    }
+
+    // Return backingSurface back to the surface pool.
+    ReturnSurface(backingSurface);
+  }));
+#endif
+}
+
+already_AddRefed<DataSourceSurface>
+ProfilerScreenshots::TakeNextSurface()
+{
+  MutexAutoLock mon(mMutex);
+  if (!mAvailableSurfaces.IsEmpty()) {
+    RefPtr<DataSourceSurface> surf = mAvailableSurfaces[0];
+    mAvailableSurfaces.RemoveElementAt(0);
+    return surf.forget();
+  }
+  if (mLiveSurfaceCount >= 8) {
+    NS_WARNING("already 8 surfaces in flight, skipping capture for this composite");
+    return nullptr;
+  }
+  mLiveSurfaceCount++;
+  return Factory::CreateDataSourceSurface(ScreenshotSize(),
+                                          SurfaceFormat::B8G8R8A8);
+}
+
+void
+ProfilerScreenshots::ReturnSurface(DataSourceSurface* aSurface)
+{
+  MutexAutoLock mon(this->mMutex);
+  mAvailableSurfaces.AppendElement(aSurface);
+}
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ProfilerScreenshots.h
@@ -0,0 +1,108 @@
+/* -*- 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_ProfilerScreenshots_h
+#define mozilla_layers_ProfilerScreenshots_h
+
+#include <functional>
+
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+
+#include "mozilla/gfx/Point.h"
+
+class nsIThread;
+
+namespace mozilla {
+
+namespace gfx {
+class DataSourceSurface;
+}
+
+namespace layers {
+
+/**
+ * Can be used to submit screenshots from the compositor to the profiler.
+ * Screenshots have a fixed bounding size. The user of this class will usually
+ * scale down the window contents first, ideally on the GPU, then read back the
+ * small scaled down image into main memory, and then call SubmitScreenshot to
+ * pass the data to the profiler.
+ * This class encodes each screenshot to a JPEG data URL, on a separate thread.
+ * This class manages that thread and recycles memory buffers.
+ */
+class ProfilerScreenshots final
+{
+public:
+  ProfilerScreenshots();
+  ~ProfilerScreenshots();
+
+  /**
+   * Returns whether the profiler is currently active and is running with the
+   * "screenshots" feature enabled.
+   */
+  static bool IsEnabled();
+
+  /**
+   * Returns a fixed size that all screenshots should be resized to fit into.
+   */
+  static gfx::IntSize ScreenshotSize() { return gfx::IntSize(350, 350); }
+
+  /**
+   * The main functionality provided by this class.
+   * This method will synchronously invoke the supplied aPopulateSurface
+   * callback function with a DataSourceSurface in which the callback should
+   * store the pixel data. This surface may be larger than aScaledSize, but
+   * only the data in the rectangle (0, 0, aScaledSize.width, aScaledSize.height)
+   * will be read.
+   * @param aWindowIdentifier A pointer-sized integer that can be used to match
+   *   up multiple screenshots from the same window.
+   * @param aOriginalSize The unscaled size of the snapshotted window.
+   * @param aScaledSize The scaled size, aScaled <= ScreenshotSize(), which the
+   *   snapshot has been resized to.
+   * @param aTimeStamp The time at which the snapshot was taken. In
+   *   asynchronous readback implementations, this is the time at which the
+   *   readback / copy command was put into the command stream to the GPU, not
+   *   the time at which the readback data was mapped into main memory.
+   * @param aPopulateSurface A callback that the caller needs to implement,
+   *   which needs to copy the screenshot pixel data into the surface that's
+   *   supplied to the callback. Called zero or one times, synchronously.
+   */
+  void SubmitScreenshot(uintptr_t aWindowIdentifier, const gfx::IntSize& aOriginalSize,
+                        const gfx::IntSize& aScaledSize, const TimeStamp& aTimeStamp,
+                        const std::function<bool(gfx::DataSourceSurface*)>& aPopulateSurface);
+
+private:
+  /**
+   * Recycle a surface from mAvailableSurfaces or create a new one if all
+   * surfaces are currently in use, up to some maximum limit.
+   * Returns null if the limit is reached.
+   * Can be called on any thread.
+   */
+  already_AddRefed<DataSourceSurface> TakeNextSurface();
+
+  /**
+   * Return aSurface back into the mAvailableSurfaces pool. Can be called on
+   * any thread.
+   */
+  void ReturnSurface(DataSourceSurface* aSurface);
+
+  // The thread on which encoding happens.
+  nsCOMPtr<nsIThread> mThread;
+  // An array of surfaces ready to be recycled. Can be accessed from multiple
+  // threads, protected by mMutex.
+  nsTArray<RefPtr<DataSourceSurface>> mAvailableSurfaces;
+  // Protects mAvailableSurfaces.
+  Mutex mMutex;
+  // The total number of surfaces created. If encoding is fast enough to happen
+  // entirely in the time between two calls to SubmitScreenshot, this should
+  // never exceed 1.
+  uint32_t mLiveSurfaceCount;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_ProfilerScreenshots_h
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -216,16 +216,17 @@ EXPORTS.mozilla.layers += [
     'opengl/CompositingRenderTargetOGL.h',
     'opengl/CompositorOGL.h',
     'opengl/MacIOSurfaceTextureClientOGL.h',
     'opengl/MacIOSurfaceTextureHostOGL.h',
     'opengl/TextureClientOGL.h',
     'opengl/TextureHostOGL.h',
     'PaintThread.h',
     'PersistentBufferProvider.h',
+    'ProfilerScreenshots.h',
     'RenderTrace.h',
     'RotatedBuffer.h',
     'ShareableCanvasRenderer.h',
     'SourceSurfaceSharedData.h',
     'SourceSurfaceVolatileData.h',
     'SyncObject.h',
     'TextureSourceProvider.h',
     'TextureWrapperImage.h',
@@ -452,16 +453,17 @@ UNIFIED_SOURCES += [
     'mlgpu/TextureSourceProviderMLGPU.cpp',
     'opengl/CompositingRenderTargetOGL.cpp',
     'opengl/CompositorOGL.cpp',
     'opengl/GLBlitTextureImageHelper.cpp',
     'opengl/OGLShaderProgram.cpp',
     'opengl/TextureClientOGL.cpp',
     'opengl/TextureHostOGL.cpp',
     'PaintThread.cpp',
+    'ProfilerScreenshots.cpp',
     'ReadbackProcessor.cpp',
     'RenderTrace.cpp',
     'RotatedBuffer.cpp',
     'ShareableCanvasRenderer.cpp',
     'SourceSurfaceSharedData.cpp',
     'SourceSurfaceVolatileData.cpp',
     'SyncObject.cpp',
     'TextureSourceProvider.cpp',
--- a/tools/profiler/core/ProfilerMarkerPayload.cpp
+++ b/tools/profiler/core/ProfilerMarkerPayload.cpp
@@ -1,13 +1,15 @@
 /* -*- 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 <inttypes.h>
+
 #include "GeckoProfiler.h"
 #include "ProfilerBacktrace.h"
 #include "ProfilerMarkerPayload.h"
 #include "gfxASurface.h"
 #include "Layers.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/Maybe.h"
 
@@ -124,16 +126,30 @@ VsyncMarkerPayload::StreamPayload(Splice
                                   UniqueStacks& aUniqueStacks)
 {
   aWriter.DoubleProperty("vsync",
                          (mVsyncTimestamp - aProcessStartTime).ToMilliseconds());
   aWriter.StringProperty("category", "VsyncTimestamp");
 }
 
 void
+ScreenshotPayload::StreamPayload(SpliceableJSONWriter& aWriter,
+                                  const TimeStamp& aProcessStartTime,
+                                  UniqueStacks& aUniqueStacks)
+{
+  aUniqueStacks.mUniqueStrings->WriteProperty(aWriter, "url", mScreenshotDataURL.get());
+
+  char hexWindowID[32];
+  SprintfLiteral(hexWindowID, "0x%" PRIXPTR, mWindowIdentifier);
+  aWriter.StringProperty("windowID", hexWindowID);
+  aWriter.DoubleProperty("windowWidth", mWindowSize.width);
+  aWriter.DoubleProperty("windowHeight", mWindowSize.height);
+}
+
+void
 GCSliceMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter,
                                     const TimeStamp& aProcessStartTime,
                                     UniqueStacks& aUniqueStacks)
 {
   MOZ_ASSERT(mTimingJSON);
   StreamCommonProps("GCSlice", aWriter, aProcessStartTime, aUniqueStacks);
   if (mTimingJSON) {
     aWriter.SplicedJSONProperty("timings", mTimingJSON.get());
--- a/tools/profiler/public/ProfilerMarkerPayload.h
+++ b/tools/profiler/public/ProfilerMarkerPayload.h
@@ -205,16 +205,37 @@ public:
   {}
 
   DECL_STREAM_PAYLOAD
 
 private:
   mozilla::TimeStamp mVsyncTimestamp;
 };
 
+class ScreenshotPayload : public ProfilerMarkerPayload
+{
+public:
+  explicit ScreenshotPayload(mozilla::TimeStamp aTimeStamp,
+                             nsCString&& aScreenshotDataURL,
+                             const mozilla::gfx::IntSize& aWindowSize,
+                             uintptr_t aWindowIdentifier)
+    : ProfilerMarkerPayload(aTimeStamp, mozilla::TimeStamp())
+    , mScreenshotDataURL(mozilla::Move(aScreenshotDataURL))
+    , mWindowSize(aWindowSize)
+    , mWindowIdentifier(aWindowIdentifier)
+  {}
+
+  DECL_STREAM_PAYLOAD
+
+private:
+  nsCString mScreenshotDataURL;
+  mozilla::gfx::IntSize mWindowSize;
+  uintptr_t mWindowIdentifier;
+};
+
 class GCSliceMarkerPayload : public ProfilerMarkerPayload
 {
 public:
   GCSliceMarkerPayload(const mozilla::TimeStamp& aStartTime,
                        const mozilla::TimeStamp& aEndTime,
                        JS::UniqueChars&& aTimingJSON)
    : ProfilerMarkerPayload(aStartTime, aEndTime),
      mTimingJSON(mozilla::Move(aTimingJSON))