diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/layers/ScreenshotGrabber.cpp | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/gfx/layers/ScreenshotGrabber.cpp b/gfx/layers/ScreenshotGrabber.cpp new file mode 100644 index 0000000000..3719afa336 --- /dev/null +++ b/gfx/layers/ScreenshotGrabber.cpp @@ -0,0 +1,229 @@ +/* -*- 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 "ScreenshotGrabber.h" + +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" + +#include "mozilla/layers/Compositor.h" +#include "mozilla/layers/ProfilerScreenshots.h" +#include "mozilla/layers/TextureHost.h" +#include "mozilla/gfx/Point.h" +#include "nsTArray.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { +namespace profiler_screenshots { + +/** + * The actual implementation of screenshot grabbing. + * The ScreenshotGrabberImpl object is destroyed if the profiler is + * disabled and MaybeGrabScreenshot notices it. + */ +class ScreenshotGrabberImpl final { + public: + explicit ScreenshotGrabberImpl(const IntSize& aBufferSize); + ~ScreenshotGrabberImpl(); + + void GrabScreenshot(Window& aWindow, const IntSize& aWindowSize); + void ProcessQueue(); + + private: + struct QueueItem final { + mozilla::TimeStamp mTimeStamp; + RefPtr<AsyncReadbackBuffer> mScreenshotBuffer; + IntSize mScreenshotSize; + IntSize mWindowSize; + }; + + RefPtr<RenderSource> ScaleDownWindowRenderSourceToSize( + Window& aWindow, const IntSize& aDestSize, + RenderSource* aWindowRenderSource, size_t aLevel); + + already_AddRefed<AsyncReadbackBuffer> TakeNextBuffer(Window& aWindow); + void ReturnBuffer(AsyncReadbackBuffer* aBuffer); + + nsTArray<RefPtr<DownscaleTarget>> mCachedLevels; + nsTArray<RefPtr<AsyncReadbackBuffer>> mAvailableBuffers; + Maybe<QueueItem> mCurrentFrameQueueItem; + nsTArray<QueueItem> mQueue; + RefPtr<ProfilerScreenshots> mProfilerScreenshots; + const IntSize mBufferSize; +}; + +} // namespace profiler_screenshots + +ScreenshotGrabber::ScreenshotGrabber() = default; + +ScreenshotGrabber::~ScreenshotGrabber() = default; + +void ScreenshotGrabber::MaybeGrabScreenshot( + profiler_screenshots::Window& aWindow, const IntSize& aWindowSize) { + if (ProfilerScreenshots::IsEnabled()) { + if (!mImpl) { + mImpl = MakeUnique<profiler_screenshots::ScreenshotGrabberImpl>( + ProfilerScreenshots::ScreenshotSize()); + } + mImpl->GrabScreenshot(aWindow, aWindowSize); + } else if (mImpl) { + Destroy(); + } +} + +void ScreenshotGrabber::MaybeProcessQueue() { + if (ProfilerScreenshots::IsEnabled()) { + if (!mImpl) { + mImpl = MakeUnique<profiler_screenshots::ScreenshotGrabberImpl>( + ProfilerScreenshots::ScreenshotSize()); + } + mImpl->ProcessQueue(); + } else if (mImpl) { + Destroy(); + } +} + +void ScreenshotGrabber::NotifyEmptyFrame() { + PROFILER_MARKER_UNTYPED("NoCompositorScreenshot because nothing changed", + GRAPHICS); +} + +void ScreenshotGrabber::Destroy() { mImpl = nullptr; } + +namespace profiler_screenshots { + +ScreenshotGrabberImpl::ScreenshotGrabberImpl(const IntSize& aBufferSize) + : mBufferSize(aBufferSize) {} + +ScreenshotGrabberImpl::~ScreenshotGrabberImpl() { + // 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 aWindowRenderSource into a RenderSource of size +// mBufferSize * (1 << aLevel) and return that RenderSource. +// 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<RenderSource> ScreenshotGrabberImpl::ScaleDownWindowRenderSourceToSize( + Window& aWindow, const IntSize& aDestSize, + RenderSource* aWindowRenderSource, size_t aLevel) { + if (aLevel == mCachedLevels.Length()) { + mCachedLevels.AppendElement( + aWindow.CreateDownscaleTarget(mBufferSize * (1 << aLevel))); + } + MOZ_RELEASE_ASSERT(aLevel < mCachedLevels.Length()); + + RefPtr<RenderSource> renderSource = aWindowRenderSource; + IntSize sourceSize = aWindowRenderSource->Size(); + if (sourceSize.width > aDestSize.width * 2) { + sourceSize = aDestSize * 2; + renderSource = ScaleDownWindowRenderSourceToSize( + aWindow, sourceSize, aWindowRenderSource, aLevel + 1); + } + + if (renderSource) { + if (mCachedLevels[aLevel]->DownscaleFrom( + renderSource, IntRect({}, sourceSize), IntRect({}, aDestSize))) { + return mCachedLevels[aLevel]->AsRenderSource(); + } + } + return nullptr; +} + +void ScreenshotGrabberImpl::GrabScreenshot(Window& aWindow, + const IntSize& aWindowSize) { + RefPtr<RenderSource> windowRenderSource = + aWindow.GetWindowContents(aWindowSize); + + if (!windowRenderSource) { + PROFILER_MARKER_UNTYPED( + "NoCompositorScreenshot because of unsupported compositor " + "configuration", + GRAPHICS); + return; + } + + Size windowSize(aWindowSize); + float scale = std::min(mBufferSize.width / windowSize.width, + mBufferSize.height / windowSize.height); + IntSize scaledSize = IntSize::Round(windowSize * scale); + RefPtr<RenderSource> scaledTarget = ScaleDownWindowRenderSourceToSize( + aWindow, scaledSize, windowRenderSource, 0); + + if (!scaledTarget) { + PROFILER_MARKER_UNTYPED( + "NoCompositorScreenshot because ScaleDownWindowRenderSourceToSize " + "failed", + GRAPHICS); + return; + } + + RefPtr<AsyncReadbackBuffer> buffer = TakeNextBuffer(aWindow); + if (!buffer) { + PROFILER_MARKER_UNTYPED( + "NoCompositorScreenshot because AsyncReadbackBuffer creation failed", + GRAPHICS); + return; + } + + buffer->CopyFrom(scaledTarget); + + // 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(), std::move(buffer), scaledSize, + windowRenderSource->Size()}); +} + +already_AddRefed<AsyncReadbackBuffer> ScreenshotGrabberImpl::TakeNextBuffer( + Window& aWindow) { + if (!mAvailableBuffers.IsEmpty()) { + RefPtr<AsyncReadbackBuffer> buffer = mAvailableBuffers[0]; + mAvailableBuffers.RemoveElementAt(0); + return buffer.forget(); + } + return aWindow.CreateAsyncReadbackBuffer(mBufferSize); +} + +void ScreenshotGrabberImpl::ReturnBuffer(AsyncReadbackBuffer* aBuffer) { + mAvailableBuffers.AppendElement(aBuffer); +} + +void ScreenshotGrabberImpl::ProcessQueue() { + if (!mQueue.IsEmpty()) { + if (!mProfilerScreenshots) { + mProfilerScreenshots = new ProfilerScreenshots(); + } + for (const auto& item : mQueue) { + mProfilerScreenshots->SubmitScreenshot( + 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(std::move(*mCurrentFrameQueueItem)); + mCurrentFrameQueueItem = Nothing(); + } +} + +} // namespace profiler_screenshots +} // namespace layers +} // namespace mozilla |