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