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 "WindowRenderer.h"
8
9 #include "gfxPlatform.h"
10 #include "mozilla/dom/Animation.h" // for Animation
11 #include "mozilla/dom/AnimationEffect.h"
12 #include "mozilla/EffectSet.h"
13 #include "mozilla/layers/PersistentBufferProvider.h" // for PersistentBufferProviderBasic, PersistentBufferProvider (ptr only)
14 #include "nsDisplayList.h"
15
16 using namespace mozilla::gfx;
17 using namespace mozilla::layers;
18
19 namespace mozilla {
20
21 /**
22 * StartFrameTimeRecording, together with StopFrameTimeRecording
23 * enable recording of frame intervals.
24 *
25 * To allow concurrent consumers, a cyclic array is used which serves all
26 * consumers, practically stateless with regard to consumers.
27 *
28 * To save resources, the buffer is allocated on first call to
29 * StartFrameTimeRecording and recording is paused if no consumer which called
30 * StartFrameTimeRecording is able to get valid results (because the cyclic
31 * buffer was overwritten since that call).
32 *
33 * To determine availability of the data upon StopFrameTimeRecording:
34 * - mRecording.mNextIndex increases on each RecordFrame, and never resets.
35 * - Cyclic buffer position is realized as mNextIndex % bufferSize.
36 * - StartFrameTimeRecording returns mNextIndex. When StopFrameTimeRecording is
37 * called, the required start index is passed as an arg, and we're able to
38 * calculate the required length. If this length is bigger than bufferSize, it
39 * means data was overwritten. otherwise, we can return the entire sequence.
40 * - To determine if we need to pause, mLatestStartIndex is updated to
41 * mNextIndex on each call to StartFrameTimeRecording. If this index gets
42 * overwritten, it means that all earlier start indices obtained via
43 * StartFrameTimeRecording were also overwritten, hence, no point in
44 * recording, so pause.
45 * - mCurrentRunStartIndex indicates the oldest index of the recording after
46 * which the recording was not paused. If StopFrameTimeRecording is invoked
47 * with a start index older than this, it means that some frames were not
48 * recorded, so data is invalid.
49 */
StartFrameTimeRecording(int32_t aBufferSize)50 uint32_t FrameRecorder::StartFrameTimeRecording(int32_t aBufferSize) {
51 if (mRecording.mIsPaused) {
52 mRecording.mIsPaused = false;
53
54 if (!mRecording.mIntervals.Length()) { // Initialize recording buffers
55 mRecording.mIntervals.SetLength(aBufferSize);
56 }
57
58 // After being paused, recent values got invalid. Update them to now.
59 mRecording.mLastFrameTime = TimeStamp::Now();
60
61 // Any recording which started before this is invalid, since we were paused.
62 mRecording.mCurrentRunStartIndex = mRecording.mNextIndex;
63 }
64
65 // If we'll overwrite this index, there are no more consumers with aStartIndex
66 // for which we're able to provide the full recording, so no point in keep
67 // recording.
68 mRecording.mLatestStartIndex = mRecording.mNextIndex;
69 return mRecording.mNextIndex;
70 }
71
RecordFrame()72 void FrameRecorder::RecordFrame() {
73 if (!mRecording.mIsPaused) {
74 TimeStamp now = TimeStamp::Now();
75 uint32_t i = mRecording.mNextIndex % mRecording.mIntervals.Length();
76 mRecording.mIntervals[i] =
77 static_cast<float>((now - mRecording.mLastFrameTime).ToMilliseconds());
78 mRecording.mNextIndex++;
79 mRecording.mLastFrameTime = now;
80
81 if (mRecording.mNextIndex >
82 (mRecording.mLatestStartIndex + mRecording.mIntervals.Length())) {
83 // We've just overwritten the most recent recording start -> pause.
84 mRecording.mIsPaused = true;
85 }
86 }
87 }
88
StopFrameTimeRecording(uint32_t aStartIndex,nsTArray<float> & aFrameIntervals)89 void FrameRecorder::StopFrameTimeRecording(uint32_t aStartIndex,
90 nsTArray<float>& aFrameIntervals) {
91 uint32_t bufferSize = mRecording.mIntervals.Length();
92 uint32_t length = mRecording.mNextIndex - aStartIndex;
93 if (mRecording.mIsPaused || length > bufferSize ||
94 aStartIndex < mRecording.mCurrentRunStartIndex) {
95 // aStartIndex is too old. Also if aStartIndex was issued before
96 // mRecordingNextIndex overflowed (uint32_t)
97 // and stopped after the overflow (would happen once every 828 days of
98 // constant 60fps).
99 length = 0;
100 }
101
102 if (!length) {
103 aFrameIntervals.Clear();
104 return; // empty recording, return empty arrays.
105 }
106 // Set length in advance to avoid possibly repeated reallocations
107 aFrameIntervals.SetLength(length);
108
109 uint32_t cyclicPos = aStartIndex % bufferSize;
110 for (uint32_t i = 0; i < length; i++, cyclicPos++) {
111 if (cyclicPos == bufferSize) {
112 cyclicPos = 0;
113 }
114 aFrameIntervals[i] = mRecording.mIntervals[cyclicPos];
115 }
116 }
117
118 already_AddRefed<PersistentBufferProvider>
CreatePersistentBufferProvider(const mozilla::gfx::IntSize & aSize,mozilla::gfx::SurfaceFormat aFormat)119 WindowRenderer::CreatePersistentBufferProvider(
120 const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat) {
121 RefPtr<PersistentBufferProviderBasic> bufferProvider;
122 // If we are using remote canvas we don't want to use acceleration in
123 // non-remote layer managers, so we always use the fallback software one.
124 if (!gfxPlatform::UseRemoteCanvas() ||
125 !gfxPlatform::IsBackendAccelerated(
126 gfxPlatform::GetPlatform()->GetPreferredCanvasBackend())) {
127 bufferProvider = PersistentBufferProviderBasic::Create(
128 aSize, aFormat,
129 gfxPlatform::GetPlatform()->GetPreferredCanvasBackend());
130 }
131
132 if (!bufferProvider) {
133 bufferProvider = PersistentBufferProviderBasic::Create(
134 aSize, aFormat, gfxPlatform::GetPlatform()->GetFallbackCanvasBackend());
135 }
136
137 return bufferProvider.forget();
138 }
139
AddPartialPrerenderedAnimation(uint64_t aCompositorAnimationId,dom::Animation * aAnimation)140 void WindowRenderer::AddPartialPrerenderedAnimation(
141 uint64_t aCompositorAnimationId, dom::Animation* aAnimation) {
142 mPartialPrerenderedAnimations.InsertOrUpdate(aCompositorAnimationId,
143 RefPtr{aAnimation});
144 aAnimation->SetPartialPrerendered(aCompositorAnimationId);
145 }
RemovePartialPrerenderedAnimation(uint64_t aCompositorAnimationId,dom::Animation * aAnimation)146 void WindowRenderer::RemovePartialPrerenderedAnimation(
147 uint64_t aCompositorAnimationId, dom::Animation* aAnimation) {
148 MOZ_ASSERT(aAnimation);
149 #ifdef DEBUG
150 RefPtr<dom::Animation> animation;
151 if (mPartialPrerenderedAnimations.Remove(aCompositorAnimationId,
152 getter_AddRefs(animation)) &&
153 // It may be possible that either animation's effect has already been
154 // nulled out via Animation::SetEffect() so ignore such cases.
155 aAnimation->GetEffect() && aAnimation->GetEffect()->AsKeyframeEffect() &&
156 animation->GetEffect() && animation->GetEffect()->AsKeyframeEffect()) {
157 MOZ_ASSERT(EffectSet::GetEffectSetForEffect(
158 aAnimation->GetEffect()->AsKeyframeEffect()) ==
159 EffectSet::GetEffectSetForEffect(
160 animation->GetEffect()->AsKeyframeEffect()));
161 }
162 #else
163 mPartialPrerenderedAnimations.Remove(aCompositorAnimationId);
164 #endif
165 aAnimation->ResetPartialPrerendered();
166 }
UpdatePartialPrerenderedAnimations(const nsTArray<uint64_t> & aJankedAnimations)167 void WindowRenderer::UpdatePartialPrerenderedAnimations(
168 const nsTArray<uint64_t>& aJankedAnimations) {
169 for (uint64_t id : aJankedAnimations) {
170 RefPtr<dom::Animation> animation;
171 if (mPartialPrerenderedAnimations.Remove(id, getter_AddRefs(animation))) {
172 animation->UpdatePartialPrerendered();
173 }
174 }
175 }
176
SetTarget(gfxContext * aTarget,layers::BufferMode aDoubleBuffering)177 void FallbackRenderer::SetTarget(gfxContext* aTarget,
178 layers::BufferMode aDoubleBuffering) {
179 mTarget = aTarget;
180 mBufferMode = aDoubleBuffering;
181 }
182
BeginTransaction(const nsCString & aURL)183 bool FallbackRenderer::BeginTransaction(const nsCString& aURL) {
184 if (!mTarget) {
185 return false;
186 }
187
188 return true;
189 }
190
EndTransactionWithColor(const nsIntRect & aRect,const gfx::DeviceColor & aColor)191 void FallbackRenderer::EndTransactionWithColor(const nsIntRect& aRect,
192 const gfx::DeviceColor& aColor) {
193 mTarget->GetDrawTarget()->FillRect(Rect(aRect), ColorPattern(aColor));
194 mAnimationReadyTime = TimeStamp::Now();
195 }
196
EndTransactionWithList(nsDisplayListBuilder * aBuilder,nsDisplayList * aList,int32_t aAppUnitsPerDevPixel,EndTransactionFlags aFlags)197 void FallbackRenderer::EndTransactionWithList(nsDisplayListBuilder* aBuilder,
198 nsDisplayList* aList,
199 int32_t aAppUnitsPerDevPixel,
200 EndTransactionFlags aFlags) {
201 if (aFlags & EndTransactionFlags::END_NO_COMPOSITE) {
202 return;
203 }
204
205 DrawTarget* dt = mTarget->GetDrawTarget();
206
207 BackendType backend = gfxPlatform::GetPlatform()->GetContentBackendFor(
208 LayersBackend::LAYERS_NONE);
209 RefPtr<DrawTarget> dest =
210 gfxPlatform::GetPlatform()->CreateDrawTargetForBackend(
211 backend, dt->GetSize(), dt->GetFormat());
212 RefPtr<gfxContext> ctx = gfxContext::CreatePreservingTransformOrNull(dest);
213
214 nsRegion opaque = aList->GetOpaqueRegion(aBuilder);
215 if (opaque.Contains(aList->GetComponentAlphaBounds(aBuilder))) {
216 dest->SetPermitSubpixelAA(true);
217 }
218
219 aList->Paint(aBuilder, ctx, aAppUnitsPerDevPixel);
220
221 RefPtr<SourceSurface> snapshot = dest->Snapshot();
222 dt->DrawSurface(snapshot, Rect(dest->GetRect()), Rect(dest->GetRect()),
223 DrawSurfaceOptions(),
224 DrawOptions(1.0f, CompositionOp::OP_SOURCE));
225 mAnimationReadyTime = TimeStamp::Now();
226 }
227
228 } // namespace mozilla
229