1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "CompositorScreenshotGrabber.h"
8 
9 #include "mozilla/RefPtr.h"
10 #include "mozilla/TimeStamp.h"
11 #include "mozilla/UniquePtr.h"
12 
13 #include "mozilla/layers/Compositor.h"
14 #include "mozilla/layers/ProfilerScreenshots.h"
15 #include "mozilla/layers/TextureHost.h"
16 #include "mozilla/gfx/Point.h"
17 #include "nsTArray.h"
18 
19 namespace mozilla {
20 
21 using namespace gfx;
22 
23 namespace layers {
24 
25 /**
26  * The actual implementation of screenshot grabbing.
27  * The CompositorScreenshotGrabberImpl object is destroyed if the profiler is
28  * disabled and MaybeGrabScreenshot notices it.
29  */
30 class CompositorScreenshotGrabberImpl final {
31  public:
32   explicit CompositorScreenshotGrabberImpl(const IntSize& aBufferSize);
33   ~CompositorScreenshotGrabberImpl();
34 
35   void GrabScreenshot(Compositor* aCompositor);
36   void ProcessQueue();
37 
38  private:
39   struct QueueItem final {
40     mozilla::TimeStamp mTimeStamp;
41     RefPtr<AsyncReadbackBuffer> mScreenshotBuffer;
42     gfx::IntSize mScreenshotSize;
43     gfx::IntSize mWindowSize;
44     uintptr_t mWindowIdentifier;
45   };
46 
47   RefPtr<CompositingRenderTarget> ScaleDownWindowTargetToSize(
48       Compositor* aCompositor, const gfx::IntSize& aDestSize,
49       CompositingRenderTarget* aWindowTarget, size_t aLevel);
50 
51   already_AddRefed<AsyncReadbackBuffer> TakeNextBuffer(Compositor* aCompositor);
52   void ReturnBuffer(AsyncReadbackBuffer* aBuffer);
53 
54   nsTArray<RefPtr<CompositingRenderTarget>> mTargets;
55   nsTArray<RefPtr<AsyncReadbackBuffer>> mAvailableBuffers;
56   Maybe<QueueItem> mCurrentFrameQueueItem;
57   nsTArray<QueueItem> mQueue;
58   RefPtr<ProfilerScreenshots> mProfilerScreenshots;
59   const IntSize mBufferSize;
60 };
61 
62 CompositorScreenshotGrabber::CompositorScreenshotGrabber() = default;
63 
64 CompositorScreenshotGrabber::~CompositorScreenshotGrabber() = default;
65 
MaybeGrabScreenshot(Compositor * aCompositor)66 void CompositorScreenshotGrabber::MaybeGrabScreenshot(Compositor* aCompositor) {
67   if (ProfilerScreenshots::IsEnabled()) {
68     if (!mImpl) {
69       mImpl = MakeUnique<CompositorScreenshotGrabberImpl>(
70           ProfilerScreenshots::ScreenshotSize());
71     }
72     mImpl->GrabScreenshot(aCompositor);
73   } else if (mImpl) {
74     Destroy();
75   }
76 }
77 
MaybeProcessQueue()78 void CompositorScreenshotGrabber::MaybeProcessQueue() {
79   if (ProfilerScreenshots::IsEnabled()) {
80     if (!mImpl) {
81       mImpl = MakeUnique<CompositorScreenshotGrabberImpl>(
82           ProfilerScreenshots::ScreenshotSize());
83     }
84     mImpl->ProcessQueue();
85   } else if (mImpl) {
86     Destroy();
87   }
88 }
89 
NotifyEmptyFrame()90 void CompositorScreenshotGrabber::NotifyEmptyFrame() {
91 #ifdef MOZ_GECKO_PROFILER
92   PROFILER_ADD_MARKER("NoCompositorScreenshot because nothing changed",
93                       GRAPHICS);
94 #endif
95 }
96 
Destroy()97 void CompositorScreenshotGrabber::Destroy() { mImpl = nullptr; }
98 
CompositorScreenshotGrabberImpl(const IntSize & aBufferSize)99 CompositorScreenshotGrabberImpl::CompositorScreenshotGrabberImpl(
100     const IntSize& aBufferSize)
101     : mBufferSize(aBufferSize) {}
102 
~CompositorScreenshotGrabberImpl()103 CompositorScreenshotGrabberImpl::~CompositorScreenshotGrabberImpl() {
104   // Any queue items in mQueue or mCurrentFrameQueueItem will be lost.
105   // That's ok: Either the profiler has stopped and we don't care about these
106   // screenshots, or the window is closing and we don't really need the last
107   // few frames from the window.
108 }
109 
110 // Scale down aWindowTarget into a CompositingRenderTarget of size
111 // mBufferSize * (1 << aLevel) and return that CompositingRenderTarget.
112 // Don't scale down by more than a factor of 2 with a single scaling operation,
113 // because it'll look bad. If higher scales are needed, use another
114 // intermediate target by calling this function recursively with aLevel + 1.
115 RefPtr<CompositingRenderTarget>
ScaleDownWindowTargetToSize(Compositor * aCompositor,const IntSize & aDestSize,CompositingRenderTarget * aWindowTarget,size_t aLevel)116 CompositorScreenshotGrabberImpl::ScaleDownWindowTargetToSize(
117     Compositor* aCompositor, const IntSize& aDestSize,
118     CompositingRenderTarget* aWindowTarget, size_t aLevel) {
119   if (aLevel == mTargets.Length()) {
120     mTargets.AppendElement(aCompositor->CreateRenderTarget(
121         IntRect(IntPoint(), mBufferSize * (1 << aLevel)), INIT_MODE_NONE));
122   }
123   MOZ_RELEASE_ASSERT(aLevel < mTargets.Length());
124 
125   RefPtr<CompositingRenderTarget> sourceTarget = aWindowTarget;
126   IntSize sourceSize = aWindowTarget->GetSize();
127   if (aWindowTarget->GetSize().width > aDestSize.width * 2) {
128     sourceSize = aDestSize * 2;
129     sourceTarget = ScaleDownWindowTargetToSize(aCompositor, sourceSize,
130                                                aWindowTarget, aLevel + 1);
131   }
132 
133   if (sourceTarget) {
134     aCompositor->SetRenderTarget(mTargets[aLevel]);
135     if (aCompositor->BlitRenderTarget(sourceTarget, sourceSize, aDestSize)) {
136       return mTargets[aLevel];
137     }
138   }
139   return nullptr;
140 }
141 
GrabScreenshot(Compositor * aCompositor)142 void CompositorScreenshotGrabberImpl::GrabScreenshot(Compositor* aCompositor) {
143   RefPtr<CompositingRenderTarget> previousTarget =
144       aCompositor->GetCurrentRenderTarget();
145 
146   RefPtr<CompositingRenderTarget> windowTarget =
147       aCompositor->GetWindowRenderTarget();
148 
149   if (!windowTarget) {
150     PROFILER_ADD_MARKER(
151         "NoCompositorScreenshot because of unsupported compositor "
152         "configuration",
153         GRAPHICS);
154     return;
155   }
156 
157   Size windowSize(windowTarget->GetSize());
158   float scale = std::min(mBufferSize.width / windowSize.width,
159                          mBufferSize.height / windowSize.height);
160   IntSize scaledSize = IntSize::Round(windowSize * scale);
161   RefPtr<CompositingRenderTarget> scaledTarget =
162       ScaleDownWindowTargetToSize(aCompositor, scaledSize, windowTarget, 0);
163 
164   // Restore the old render target.
165   aCompositor->SetRenderTarget(previousTarget);
166 
167   if (!scaledTarget) {
168     PROFILER_ADD_MARKER(
169         "NoCompositorScreenshot because ScaleDownWindowTargetToSize failed",
170         GRAPHICS);
171     return;
172   }
173 
174   RefPtr<AsyncReadbackBuffer> buffer = TakeNextBuffer(aCompositor);
175   if (!buffer) {
176     PROFILER_ADD_MARKER(
177         "NoCompositorScreenshot because AsyncReadbackBuffer creation failed",
178         GRAPHICS);
179     return;
180   }
181 
182   aCompositor->ReadbackRenderTarget(scaledTarget, buffer);
183 
184   // This QueueItem will be added to the queue at the end of the next call to
185   // ProcessQueue(). This ensures that the buffer isn't mapped into main memory
186   // until the next frame. If we did it in this frame, we'd block on the GPU.
187   mCurrentFrameQueueItem = Some(QueueItem{
188       TimeStamp::Now(), std::move(buffer), scaledSize, windowTarget->GetSize(),
189       reinterpret_cast<uintptr_t>(static_cast<void*>(this))});
190 }
191 
192 already_AddRefed<AsyncReadbackBuffer>
TakeNextBuffer(Compositor * aCompositor)193 CompositorScreenshotGrabberImpl::TakeNextBuffer(Compositor* aCompositor) {
194   if (!mAvailableBuffers.IsEmpty()) {
195     RefPtr<AsyncReadbackBuffer> buffer = mAvailableBuffers[0];
196     mAvailableBuffers.RemoveElementAt(0);
197     return buffer.forget();
198   }
199   return aCompositor->CreateAsyncReadbackBuffer(mBufferSize);
200 }
201 
ReturnBuffer(AsyncReadbackBuffer * aBuffer)202 void CompositorScreenshotGrabberImpl::ReturnBuffer(
203     AsyncReadbackBuffer* aBuffer) {
204   mAvailableBuffers.AppendElement(aBuffer);
205 }
206 
ProcessQueue()207 void CompositorScreenshotGrabberImpl::ProcessQueue() {
208   if (!mQueue.IsEmpty()) {
209     if (!mProfilerScreenshots) {
210       mProfilerScreenshots = new ProfilerScreenshots();
211     }
212     for (const auto& item : mQueue) {
213       mProfilerScreenshots->SubmitScreenshot(
214           item.mWindowIdentifier, item.mWindowSize, item.mScreenshotSize,
215           item.mTimeStamp, [&item](DataSourceSurface* aTargetSurface) {
216             return item.mScreenshotBuffer->MapAndCopyInto(aTargetSurface,
217                                                           item.mScreenshotSize);
218           });
219       ReturnBuffer(item.mScreenshotBuffer);
220     }
221   }
222   mQueue.Clear();
223 
224   if (mCurrentFrameQueueItem) {
225     mQueue.AppendElement(std::move(*mCurrentFrameQueueItem));
226     mCurrentFrameQueueItem = Nothing();
227   }
228 }
229 
230 }  // namespace layers
231 }  // namespace mozilla
232