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