Bug 1444432 - Implement screenshot grabbing in LayerManagerComposite. r?jrmuizel
MozReview-Commit-ID: 4BNKNwCACUB
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',