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