/* -*- 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 mScreenshotBuffer; gfx::IntSize mScreenshotSize; gfx::IntSize mWindowSize; uintptr_t mWindowIdentifier; }; RefPtr ScaleDownWindowTargetToSize(Compositor* aCompositor, const gfx::IntSize& aDestSize, CompositingRenderTarget* aWindowTarget, size_t aLevel); already_AddRefed TakeNextBuffer(Compositor* aCompositor); void ReturnBuffer(AsyncReadbackBuffer* aBuffer); nsTArray> mTargets; nsTArray> mAvailableBuffers; Maybe mCurrentFrameQueueItem; nsTArray mQueue; UniquePtr mProfilerScreenshots; const IntSize mBufferSize; }; CompositorScreenshotGrabber::CompositorScreenshotGrabber() { } CompositorScreenshotGrabber::~CompositorScreenshotGrabber() { } void CompositorScreenshotGrabber::MaybeGrabScreenshot(Compositor* aCompositor) { if (ProfilerScreenshots::IsEnabled()) { if (!mImpl) { mImpl = MakeUnique(ProfilerScreenshots::ScreenshotSize()); } mImpl->GrabScreenshot(aCompositor); } else if (mImpl) { Destroy(); } } void CompositorScreenshotGrabber::MaybeProcessQueue() { if (ProfilerScreenshots::IsEnabled()) { if (!mImpl) { mImpl = MakeUnique(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 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 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 previousTarget = aCompositor->GetCurrentRenderTarget(); RefPtr 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 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 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(static_cast(this)) }); } already_AddRefed CompositorScreenshotGrabberImpl::TakeNextBuffer(Compositor* aCompositor) { if (!mAvailableBuffers.IsEmpty()) { RefPtr 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(); } 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