/src/mozilla-central/image/AnimationSurfaceProvider.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "AnimationSurfaceProvider.h" |
7 | | |
8 | | #include "gfxPrefs.h" |
9 | | #include "nsProxyRelease.h" |
10 | | |
11 | | #include "DecodePool.h" |
12 | | #include "Decoder.h" |
13 | | |
14 | | using namespace mozilla::gfx; |
15 | | |
16 | | namespace mozilla { |
17 | | namespace image { |
18 | | |
19 | | AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull<RasterImage*> aImage, |
20 | | const SurfaceKey& aSurfaceKey, |
21 | | NotNull<Decoder*> aDecoder, |
22 | | size_t aCurrentFrame) |
23 | | : ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey, |
24 | | AvailabilityState::StartAsPlaceholder()) |
25 | | , mImage(aImage.get()) |
26 | | , mDecodingMutex("AnimationSurfaceProvider::mDecoder") |
27 | | , mDecoder(aDecoder.get()) |
28 | | , mFramesMutex("AnimationSurfaceProvider::mFrames") |
29 | 0 | { |
30 | 0 | MOZ_ASSERT(!mDecoder->IsMetadataDecode(), |
31 | 0 | "Use MetadataDecodingTask for metadata decodes"); |
32 | 0 | MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(), |
33 | 0 | "Use DecodedSurfaceProvider for single-frame image decodes"); |
34 | 0 |
|
35 | 0 | // We may produce paletted surfaces for GIF which means the frames are smaller |
36 | 0 | // than one would expect. |
37 | 0 | size_t pixelSize = !aDecoder->ShouldBlendAnimation() && |
38 | 0 | aDecoder->GetType() == DecoderType::GIF |
39 | 0 | ? sizeof(uint8_t) : sizeof(uint32_t); |
40 | 0 |
|
41 | 0 | // Calculate how many frames we need to decode in this animation before we |
42 | 0 | // enter decode-on-demand mode. |
43 | 0 | IntSize frameSize = aSurfaceKey.Size(); |
44 | 0 | size_t threshold = |
45 | 0 | (size_t(gfxPrefs::ImageAnimatedDecodeOnDemandThresholdKB()) * 1024) / |
46 | 0 | (pixelSize * frameSize.width * frameSize.height); |
47 | 0 | size_t batch = gfxPrefs::ImageAnimatedDecodeOnDemandBatchSize(); |
48 | 0 |
|
49 | 0 | mFrames.Initialize(threshold, batch, aCurrentFrame); |
50 | 0 | } |
51 | | |
52 | | AnimationSurfaceProvider::~AnimationSurfaceProvider() |
53 | 0 | { |
54 | 0 | DropImageReference(); |
55 | 0 | } |
56 | | |
57 | | void |
58 | | AnimationSurfaceProvider::DropImageReference() |
59 | 0 | { |
60 | 0 | if (!mImage) { |
61 | 0 | return; // Nothing to do. |
62 | 0 | } |
63 | 0 | |
64 | 0 | // RasterImage objects need to be destroyed on the main thread. |
65 | 0 | NS_ReleaseOnMainThreadSystemGroup("AnimationSurfaceProvider::mImage", |
66 | 0 | mImage.forget()); |
67 | 0 | } |
68 | | |
69 | | void |
70 | | AnimationSurfaceProvider::Reset() |
71 | 0 | { |
72 | 0 | // We want to go back to the beginning. |
73 | 0 | bool mayDiscard; |
74 | 0 | bool restartDecoder; |
75 | 0 |
|
76 | 0 | { |
77 | 0 | MutexAutoLock lock(mFramesMutex); |
78 | 0 |
|
79 | 0 | // If we have not crossed the threshold, we know we haven't discarded any |
80 | 0 | // frames, and thus we know it is safe move our display index back to the |
81 | 0 | // very beginning. It would be cleaner to let the frame buffer make this |
82 | 0 | // decision inside the AnimationFrameBuffer::Reset method, but if we have |
83 | 0 | // crossed the threshold, we need to hold onto the decoding mutex too. We |
84 | 0 | // should avoid blocking the main thread on the decoder threads. |
85 | 0 | mayDiscard = mFrames.MayDiscard(); |
86 | 0 | if (!mayDiscard) { |
87 | 0 | restartDecoder = mFrames.Reset(); |
88 | 0 | } |
89 | 0 | } |
90 | 0 |
|
91 | 0 | if (mayDiscard) { |
92 | 0 | // We are over the threshold and have started discarding old frames. In |
93 | 0 | // that case we need to seize the decoding mutex. Thankfully we know that |
94 | 0 | // we are in the process of decoding at most the batch size frames, so |
95 | 0 | // this should not take too long to acquire. |
96 | 0 | MutexAutoLock lock(mDecodingMutex); |
97 | 0 |
|
98 | 0 | // Recreate the decoder so we can regenerate the frames again. |
99 | 0 | mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder); |
100 | 0 | MOZ_ASSERT(mDecoder); |
101 | 0 |
|
102 | 0 | MutexAutoLock lock2(mFramesMutex); |
103 | 0 | restartDecoder = mFrames.Reset(); |
104 | 0 | } |
105 | 0 |
|
106 | 0 | if (restartDecoder) { |
107 | 0 | DecodePool::Singleton()->AsyncRun(this); |
108 | 0 | } |
109 | 0 | } |
110 | | |
111 | | void |
112 | | AnimationSurfaceProvider::Advance(size_t aFrame) |
113 | 0 | { |
114 | 0 | bool restartDecoder; |
115 | 0 |
|
116 | 0 | { |
117 | 0 | // Typical advancement of a frame. |
118 | 0 | MutexAutoLock lock(mFramesMutex); |
119 | 0 | restartDecoder = mFrames.AdvanceTo(aFrame); |
120 | 0 | } |
121 | 0 |
|
122 | 0 | if (restartDecoder) { |
123 | 0 | DecodePool::Singleton()->AsyncRun(this); |
124 | 0 | } |
125 | 0 | } |
126 | | |
127 | | DrawableFrameRef |
128 | | AnimationSurfaceProvider::DrawableRef(size_t aFrame) |
129 | 0 | { |
130 | 0 | MutexAutoLock lock(mFramesMutex); |
131 | 0 |
|
132 | 0 | if (Availability().IsPlaceholder()) { |
133 | 0 | MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() on a placeholder"); |
134 | 0 | return DrawableFrameRef(); |
135 | 0 | } |
136 | 0 |
|
137 | 0 | imgFrame* frame = mFrames.Get(aFrame); |
138 | 0 | if (!frame) { |
139 | 0 | return DrawableFrameRef(); |
140 | 0 | } |
141 | 0 | |
142 | 0 | return frame->DrawableRef(); |
143 | 0 | } |
144 | | |
145 | | RawAccessFrameRef |
146 | | AnimationSurfaceProvider::RawAccessRef(size_t aFrame) |
147 | 0 | { |
148 | 0 | MutexAutoLock lock(mFramesMutex); |
149 | 0 |
|
150 | 0 | if (Availability().IsPlaceholder()) { |
151 | 0 | MOZ_ASSERT_UNREACHABLE("Calling RawAccessRef() on a placeholder"); |
152 | 0 | return RawAccessFrameRef(); |
153 | 0 | } |
154 | 0 |
|
155 | 0 | imgFrame* frame = mFrames.Get(aFrame); |
156 | 0 | if (!frame) { |
157 | 0 | return RawAccessFrameRef(); |
158 | 0 | } |
159 | 0 | |
160 | 0 | return frame->RawAccessRef(/* aOnlyFinished */ true); |
161 | 0 | } |
162 | | |
163 | | bool |
164 | | AnimationSurfaceProvider::IsFinished() const |
165 | 0 | { |
166 | 0 | MutexAutoLock lock(mFramesMutex); |
167 | 0 |
|
168 | 0 | if (Availability().IsPlaceholder()) { |
169 | 0 | MOZ_ASSERT_UNREACHABLE("Calling IsFinished() on a placeholder"); |
170 | 0 | return false; |
171 | 0 | } |
172 | 0 |
|
173 | 0 | if (mFrames.Frames().IsEmpty()) { |
174 | 0 | MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no frames"); |
175 | 0 | return false; |
176 | 0 | } |
177 | 0 |
|
178 | 0 | // As long as we have at least one finished frame, we're finished. |
179 | 0 | return mFrames.Frames()[0]->IsFinished(); |
180 | 0 | } |
181 | | |
182 | | bool |
183 | | AnimationSurfaceProvider::IsFullyDecoded() const |
184 | 0 | { |
185 | 0 | MutexAutoLock lock(mFramesMutex); |
186 | 0 | return mFrames.SizeKnown() && !mFrames.MayDiscard(); |
187 | 0 | } |
188 | | |
189 | | size_t |
190 | | AnimationSurfaceProvider::LogicalSizeInBytes() const |
191 | 0 | { |
192 | 0 | // When decoding animated images, we need at most three live surfaces: the |
193 | 0 | // composited surface, the previous composited surface for |
194 | 0 | // DisposalMethod::RESTORE_PREVIOUS, and the surface we're currently decoding |
195 | 0 | // into. The composited surfaces are always BGRA. Although the surface we're |
196 | 0 | // decoding into may be paletted, and may be smaller than the real size of the |
197 | 0 | // image, we assume the worst case here. |
198 | 0 | // XXX(seth): Note that this is actually not accurate yet; we're storing the |
199 | 0 | // full sequence of frames, not just the three live surfaces mentioned above. |
200 | 0 | // Unfortunately there's no way to know in advance how many frames an |
201 | 0 | // animation has, so we really can't do better here. This will become correct |
202 | 0 | // once bug 1289954 is complete. |
203 | 0 | IntSize size = GetSurfaceKey().Size(); |
204 | 0 | return 3 * size.width * size.height * sizeof(uint32_t); |
205 | 0 | } |
206 | | |
207 | | void |
208 | | AnimationSurfaceProvider::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, |
209 | | size_t& aHeapSizeOut, |
210 | | size_t& aNonHeapSizeOut, |
211 | | size_t& aExtHandlesOut) |
212 | 0 | { |
213 | 0 | // Note that the surface cache lock is already held here, and then we acquire |
214 | 0 | // mFramesMutex. For this method, this ordering is unavoidable, which means |
215 | 0 | // that we must be careful to always use the same ordering elsewhere. |
216 | 0 | MutexAutoLock lock(mFramesMutex); |
217 | 0 |
|
218 | 0 | for (const RawAccessFrameRef& frame : mFrames.Frames()) { |
219 | 0 | if (frame) { |
220 | 0 | frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, |
221 | 0 | aNonHeapSizeOut, aExtHandlesOut); |
222 | 0 | } |
223 | 0 | } |
224 | 0 | } |
225 | | |
226 | | void |
227 | | AnimationSurfaceProvider::Run() |
228 | 0 | { |
229 | 0 | MutexAutoLock lock(mDecodingMutex); |
230 | 0 |
|
231 | 0 | if (!mDecoder) { |
232 | 0 | MOZ_ASSERT_UNREACHABLE("Running after decoding finished?"); |
233 | 0 | return; |
234 | 0 | } |
235 | 0 |
|
236 | 0 | while (true) { |
237 | 0 | // Run the decoder. |
238 | 0 | LexerResult result = mDecoder->Decode(WrapNotNull(this)); |
239 | 0 |
|
240 | 0 | if (result.is<TerminalState>()) { |
241 | 0 | // We may have a new frame now, but it's not guaranteed - a decoding |
242 | 0 | // failure or truncated data may mean that no new frame got produced. |
243 | 0 | // Since we're not sure, rather than call CheckForNewFrameAtYield() here |
244 | 0 | // we call CheckForNewFrameAtTerminalState(), which handles both of these |
245 | 0 | // possibilities. |
246 | 0 | bool continueDecoding = CheckForNewFrameAtTerminalState(); |
247 | 0 | FinishDecoding(); |
248 | 0 |
|
249 | 0 | // Even if it is the last frame, we may not have enough frames buffered |
250 | 0 | // ahead of the current. If we are shutting down, we want to ensure we |
251 | 0 | // release the thread as soon as possible. The animation may advance even |
252 | 0 | // during shutdown, which keeps us decoding, and thus blocking the decode |
253 | 0 | // pool during teardown. |
254 | 0 | if (!mDecoder || !continueDecoding || |
255 | 0 | DecodePool::Singleton()->IsShuttingDown()) { |
256 | 0 | return; |
257 | 0 | } |
258 | 0 | |
259 | 0 | // Restart from the very beginning because the decoder was recreated. |
260 | 0 | continue; |
261 | 0 | } |
262 | 0 | |
263 | 0 | // Notify for the progress we've made so far. |
264 | 0 | if (mImage && mDecoder->HasProgress()) { |
265 | 0 | NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder)); |
266 | 0 | } |
267 | 0 |
|
268 | 0 | if (result == LexerResult(Yield::NEED_MORE_DATA)) { |
269 | 0 | // We can't make any more progress right now. The decoder itself will ensure |
270 | 0 | // that we get reenqueued when more data is available; just return for now. |
271 | 0 | return; |
272 | 0 | } |
273 | 0 | |
274 | 0 | // There's new output available - a new frame! Grab it. If we don't need any |
275 | 0 | // more for the moment we can break out of the loop. If we are shutting |
276 | 0 | // down, we want to ensure we release the thread as soon as possible. The |
277 | 0 | // animation may advance even during shutdown, which keeps us decoding, and |
278 | 0 | // thus blocking the decode pool during teardown. |
279 | 0 | MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE)); |
280 | 0 | if (!CheckForNewFrameAtYield() || |
281 | 0 | DecodePool::Singleton()->IsShuttingDown()) { |
282 | 0 | return; |
283 | 0 | } |
284 | 0 | } |
285 | 0 | } |
286 | | |
287 | | bool |
288 | | AnimationSurfaceProvider::CheckForNewFrameAtYield() |
289 | 0 | { |
290 | 0 | mDecodingMutex.AssertCurrentThreadOwns(); |
291 | 0 | MOZ_ASSERT(mDecoder); |
292 | 0 |
|
293 | 0 | bool justGotFirstFrame = false; |
294 | 0 | bool continueDecoding; |
295 | 0 |
|
296 | 0 | { |
297 | 0 | MutexAutoLock lock(mFramesMutex); |
298 | 0 |
|
299 | 0 | // Try to get the new frame from the decoder. |
300 | 0 | RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef(); |
301 | 0 | MOZ_ASSERT(mDecoder->HasFrameToTake()); |
302 | 0 | mDecoder->ClearHasFrameToTake(); |
303 | 0 |
|
304 | 0 | if (!frame) { |
305 | 0 | MOZ_ASSERT_UNREACHABLE("Decoder yielded but didn't produce a frame?"); |
306 | 0 | return true; |
307 | 0 | } |
308 | 0 |
|
309 | 0 | // We should've gotten a different frame than last time. |
310 | 0 | MOZ_ASSERT_IF(!mFrames.Frames().IsEmpty(), |
311 | 0 | mFrames.Frames().LastElement().get() != frame.get()); |
312 | 0 |
|
313 | 0 | // Append the new frame to the list. |
314 | 0 | continueDecoding = mFrames.Insert(std::move(frame)); |
315 | 0 |
|
316 | 0 | // We only want to handle the first frame if it is the first pass for the |
317 | 0 | // animation decoder. The owning image will be cleared after that. |
318 | 0 | size_t frameCount = mFrames.Frames().Length(); |
319 | 0 | if (frameCount == 1 && mImage) { |
320 | 0 | justGotFirstFrame = true; |
321 | 0 | } |
322 | 0 | } |
323 | 0 |
|
324 | 0 | if (justGotFirstFrame) { |
325 | 0 | AnnounceSurfaceAvailable(); |
326 | 0 | } |
327 | 0 |
|
328 | 0 | return continueDecoding; |
329 | 0 | } |
330 | | |
331 | | bool |
332 | | AnimationSurfaceProvider::CheckForNewFrameAtTerminalState() |
333 | 0 | { |
334 | 0 | mDecodingMutex.AssertCurrentThreadOwns(); |
335 | 0 | MOZ_ASSERT(mDecoder); |
336 | 0 |
|
337 | 0 | bool justGotFirstFrame = false; |
338 | 0 | bool continueDecoding; |
339 | 0 |
|
340 | 0 | { |
341 | 0 | MutexAutoLock lock(mFramesMutex); |
342 | 0 |
|
343 | 0 | // The decoder may or may not have a new frame for us at this point. Avoid |
344 | 0 | // reinserting the same frame again. |
345 | 0 | RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef(); |
346 | 0 |
|
347 | 0 | // If the decoder didn't finish a new frame (ie if, after starting the |
348 | 0 | // frame, it got an error and aborted the frame and the rest of the decode) |
349 | 0 | // that means it won't be reporting it to the image or FrameAnimator so we |
350 | 0 | // should ignore it too, that's what HasFrameToTake tracks basically. |
351 | 0 | if (!mDecoder->HasFrameToTake()) { |
352 | 0 | frame = RawAccessFrameRef(); |
353 | 0 | } else { |
354 | 0 | MOZ_ASSERT(frame); |
355 | 0 | mDecoder->ClearHasFrameToTake(); |
356 | 0 | } |
357 | 0 |
|
358 | 0 | if (!frame || (!mFrames.Frames().IsEmpty() && |
359 | 0 | mFrames.Frames().LastElement().get() == frame.get())) { |
360 | 0 | return mFrames.MarkComplete(); |
361 | 0 | } |
362 | 0 | |
363 | 0 | // Append the new frame to the list. |
364 | 0 | mFrames.Insert(std::move(frame)); |
365 | 0 | continueDecoding = mFrames.MarkComplete(); |
366 | 0 |
|
367 | 0 | // We only want to handle the first frame if it is the first pass for the |
368 | 0 | // animation decoder. The owning image will be cleared after that. |
369 | 0 | if (mFrames.Frames().Length() == 1 && mImage) { |
370 | 0 | justGotFirstFrame = true; |
371 | 0 | } |
372 | 0 | } |
373 | 0 |
|
374 | 0 | if (justGotFirstFrame) { |
375 | 0 | AnnounceSurfaceAvailable(); |
376 | 0 | } |
377 | 0 |
|
378 | 0 | return continueDecoding; |
379 | 0 | } |
380 | | |
381 | | void |
382 | | AnimationSurfaceProvider::AnnounceSurfaceAvailable() |
383 | 0 | { |
384 | 0 | mFramesMutex.AssertNotCurrentThreadOwns(); |
385 | 0 | MOZ_ASSERT(mImage); |
386 | 0 |
|
387 | 0 | // We just got the first frame; let the surface cache know. We deliberately do |
388 | 0 | // this outside of mFramesMutex to avoid a potential deadlock with |
389 | 0 | // AddSizeOfExcludingThis(), since otherwise we'd be acquiring mFramesMutex |
390 | 0 | // and then the surface cache lock, while the memory reporting code would |
391 | 0 | // acquire the surface cache lock and then mFramesMutex. |
392 | 0 | SurfaceCache::SurfaceAvailable(WrapNotNull(this)); |
393 | 0 | } |
394 | | |
395 | | void |
396 | | AnimationSurfaceProvider::FinishDecoding() |
397 | 0 | { |
398 | 0 | mDecodingMutex.AssertCurrentThreadOwns(); |
399 | 0 | MOZ_ASSERT(mDecoder); |
400 | 0 |
|
401 | 0 | if (mImage) { |
402 | 0 | // Send notifications. |
403 | 0 | NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder)); |
404 | 0 | } |
405 | 0 |
|
406 | 0 | // Determine if we need to recreate the decoder, in case we are discarding |
407 | 0 | // frames and need to loop back to the beginning. |
408 | 0 | bool recreateDecoder; |
409 | 0 | { |
410 | 0 | MutexAutoLock lock(mFramesMutex); |
411 | 0 | recreateDecoder = !mFrames.HasRedecodeError() && mFrames.MayDiscard(); |
412 | 0 | } |
413 | 0 |
|
414 | 0 | if (recreateDecoder) { |
415 | 0 | mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder); |
416 | 0 | MOZ_ASSERT(mDecoder); |
417 | 0 | } else { |
418 | 0 | mDecoder = nullptr; |
419 | 0 | } |
420 | 0 |
|
421 | 0 | // We don't need a reference to our image anymore, either, and we don't want |
422 | 0 | // one. We may be stored in the surface cache for a long time after decoding |
423 | 0 | // finishes. If we don't drop our reference to the image, we'll end up |
424 | 0 | // keeping it alive as long as we remain in the surface cache, which could |
425 | 0 | // greatly extend the image's lifetime - in fact, if the image isn't |
426 | 0 | // discardable, it'd result in a leak! |
427 | 0 | DropImageReference(); |
428 | 0 | } |
429 | | |
430 | | bool |
431 | | AnimationSurfaceProvider::ShouldPreferSyncRun() const |
432 | 0 | { |
433 | 0 | MutexAutoLock lock(mDecodingMutex); |
434 | 0 | MOZ_ASSERT(mDecoder); |
435 | 0 |
|
436 | 0 | return mDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime()); |
437 | 0 | } |
438 | | |
439 | | } // namespace image |
440 | | } // namespace mozilla |