ScreenshotGrabber.cpp (7581B)
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 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 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 94 void ScreenshotGrabber::NotifyEmptyFrame() { 95 PROFILER_MARKER_UNTYPED("NoCompositorScreenshot because nothing changed", 96 GRAPHICS); 97 } 98 99 void ScreenshotGrabber::Destroy() { mImpl = nullptr; } 100 101 namespace profiler_screenshots { 102 103 ScreenshotGrabberImpl::ScreenshotGrabberImpl(const IntSize& aBufferSize) 104 : mBufferSize(aBufferSize) {} 105 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. 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 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 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 200 void ScreenshotGrabberImpl::ReturnBuffer(AsyncReadbackBuffer* aBuffer) { 201 mAvailableBuffers.AppendElement(aBuffer); 202 } 203 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