FrameAnimator.cpp (20365B)
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 "FrameAnimator.h" 7 8 #include <utility> 9 10 #include "LookupResult.h" 11 #include "RasterImage.h" 12 #include "imgIContainer.h" 13 #include "mozilla/CheckedInt.h" 14 #include "mozilla/ProfilerLabels.h" 15 #include "mozilla/StaticPrefs_image.h" 16 17 namespace mozilla { 18 19 using namespace gfx; 20 21 namespace image { 22 23 /////////////////////////////////////////////////////////////////////////////// 24 // AnimationState implementation. 25 /////////////////////////////////////////////////////////////////////////////// 26 27 const gfx::IntRect AnimationState::UpdateState( 28 RasterImage* aImage, const gfx::IntSize& aSize, 29 bool aAllowInvalidation /* = true */) { 30 LookupResult result = SurfaceCache::Lookup( 31 ImageKey(aImage), 32 RasterSurfaceKey(aSize, DefaultSurfaceFlags(), PlaybackType::eAnimated), 33 /* aMarkUsed = */ false); 34 35 return UpdateStateInternal(result, aSize, aAllowInvalidation); 36 } 37 38 const gfx::IntRect AnimationState::UpdateStateInternal( 39 LookupResult& aResult, const gfx::IntSize& aSize, 40 bool aAllowInvalidation /* = true */) { 41 // Update mDiscarded and mIsCurrentlyDecoded. 42 if (aResult.Type() == MatchType::NOT_FOUND) { 43 // no frames, we've either been discarded, or never been decoded before. 44 mDiscarded = mHasBeenDecoded; 45 mIsCurrentlyDecoded = false; 46 } else if (aResult.Type() == MatchType::PENDING) { 47 // no frames yet, but a decoder is or will be working on it. 48 mDiscarded = false; 49 mIsCurrentlyDecoded = false; 50 mHasRequestedDecode = true; 51 } else { 52 MOZ_ASSERT(aResult.Type() == MatchType::EXACT); 53 mDiscarded = false; 54 mHasRequestedDecode = true; 55 56 // If we can seek to the current animation frame we consider it decoded. 57 // Animated images are never fully decoded unless very short. 58 // Note that we use GetFrame instead of Seek here. The difference is that 59 // Seek eventually calls AnimationFrameBuffer::Get with aForDisplay == true, 60 // whereas GetFrame calls AnimationFrameBuffer::Get with aForDisplay == 61 // false. The aForDisplay can change whether those functions succeed or not 62 // (only for the first frame). Since this is not for display we want to pass 63 // aForDisplay == false, but also for consistency with 64 // RequestRefresh/AdvanceFrame, because we want our state to be in sync with 65 // those functions. The user of Seek (GetCompositedFrame) doesn't need to be 66 // in sync with our state. 67 RefPtr<imgFrame> currentFrame = 68 bool(aResult.Surface()) 69 ? aResult.Surface().GetFrame(mCurrentAnimationFrameIndex) 70 : nullptr; 71 mIsCurrentlyDecoded = !!currentFrame; 72 } 73 74 gfx::IntRect ret; 75 76 if (aAllowInvalidation) { 77 // Update the value of mCompositedFrameInvalid. 78 if (mIsCurrentlyDecoded) { 79 // It is safe to clear mCompositedFrameInvalid safe to do for images that 80 // are fully decoded but aren't finished animating because before we paint 81 // the refresh driver will call into us to advance to the correct frame, 82 // and that will succeed because we have all the frames. 83 if (mCompositedFrameInvalid) { 84 // Invalidate if we are marking the composited frame valid. 85 ret.SizeTo(aSize); 86 } 87 mCompositedFrameInvalid = false; 88 } else { 89 if (mHasRequestedDecode) { 90 MOZ_ASSERT(StaticPrefs::image_mem_animated_discardable_AtStartup()); 91 mCompositedFrameInvalid = true; 92 } 93 } 94 // Otherwise don't change the value of mCompositedFrameInvalid, it will be 95 // updated by RequestRefresh. 96 } 97 98 return ret; 99 } 100 101 void AnimationState::NotifyDecodeComplete() { mHasBeenDecoded = true; } 102 103 void AnimationState::ResetAnimation() { mCurrentAnimationFrameIndex = 0; } 104 105 void AnimationState::SetAnimationMode(uint16_t aAnimationMode) { 106 mAnimationMode = aAnimationMode; 107 } 108 109 void AnimationState::UpdateKnownFrameCount(uint32_t aFrameCount) { 110 if (aFrameCount <= mFrameCount) { 111 // Nothing to do. Since we can redecode animated images, we may see the same 112 // sequence of updates replayed again, so seeing a smaller frame count than 113 // what we already know about doesn't indicate an error. 114 return; 115 } 116 117 MOZ_ASSERT(!mHasBeenDecoded, "Adding new frames after decoding is finished?"); 118 MOZ_ASSERT(aFrameCount <= mFrameCount + 1, "Skipped a frame?"); 119 120 mFrameCount = aFrameCount; 121 } 122 123 Maybe<uint32_t> AnimationState::FrameCount() const { 124 return mHasBeenDecoded ? Some(mFrameCount) : Nothing(); 125 } 126 127 void AnimationState::SetFirstFrameRefreshArea(const IntRect& aRefreshArea) { 128 mFirstFrameRefreshArea = aRefreshArea; 129 } 130 131 void AnimationState::InitAnimationFrameTimeIfNecessary() { 132 if (mCurrentAnimationFrameTime.IsNull()) { 133 mCurrentAnimationFrameTime = TimeStamp::Now(); 134 } 135 } 136 137 void AnimationState::SetAnimationFrameTime(const TimeStamp& aTime) { 138 mCurrentAnimationFrameTime = aTime; 139 } 140 141 bool AnimationState::MaybeAdvanceAnimationFrameTime(const TimeStamp& aTime) { 142 if (!StaticPrefs::image_animated_resume_from_last_displayed() || 143 mCurrentAnimationFrameTime >= aTime) { 144 return false; 145 } 146 147 // We are configured to stop an animation when it is out of view, and restart 148 // it from the same point when it comes back into view. The same applies if it 149 // was discarded while out of view. 150 mCurrentAnimationFrameTime = aTime; 151 return true; 152 } 153 154 uint32_t AnimationState::GetCurrentAnimationFrameIndex() const { 155 return mCurrentAnimationFrameIndex; 156 } 157 158 FrameTimeout AnimationState::LoopLength() const { 159 // If we don't know the loop length yet, we have to treat it as infinite. 160 if (!mLoopLength) { 161 return FrameTimeout::Forever(); 162 } 163 164 MOZ_ASSERT(mHasBeenDecoded, 165 "We know the loop length but decoding isn't done?"); 166 167 // If we're not looping, a single loop time has no meaning. 168 if (mAnimationMode != imgIContainer::kNormalAnimMode) { 169 return FrameTimeout::Forever(); 170 } 171 172 return *mLoopLength; 173 } 174 175 /////////////////////////////////////////////////////////////////////////////// 176 // FrameAnimator implementation. 177 /////////////////////////////////////////////////////////////////////////////// 178 179 TimeStamp FrameAnimator::GetCurrentImgFrameEndTime( 180 AnimationState& aState, FrameTimeout aCurrentTimeout) const { 181 if (aCurrentTimeout == FrameTimeout::Forever()) { 182 // We need to return a sentinel value in this case, because our logic 183 // doesn't work correctly if we have an infinitely long timeout. We use one 184 // year in the future as the sentinel because it works with the loop in 185 // RequestRefresh() below. 186 // XXX(seth): It'd be preferable to make our logic work correctly with 187 // infinitely long timeouts. 188 return TimeStamp::NowLoRes() + TimeDuration::FromMilliseconds(31536000.0); 189 } 190 191 TimeDuration durationOfTimeout = 192 TimeDuration::FromMilliseconds(double(aCurrentTimeout.AsMilliseconds())); 193 return aState.mCurrentAnimationFrameTime + durationOfTimeout; 194 } 195 196 RefreshResult FrameAnimator::AdvanceFrame(AnimationState& aState, 197 DrawableSurface& aFrames, 198 RefPtr<imgFrame>& aCurrentFrame, 199 TimeStamp aTime) { 200 AUTO_PROFILER_LABEL("FrameAnimator::AdvanceFrame", GRAPHICS); 201 202 RefreshResult ret; 203 204 // Determine what the next frame is, taking into account looping. 205 uint32_t currentFrameIndex = aState.mCurrentAnimationFrameIndex; 206 uint32_t nextFrameIndex = currentFrameIndex + 1; 207 208 // Check if we're at the end of the loop. (FrameCount() returns Nothing() if 209 // we don't know the total count yet.) 210 if (aState.FrameCount() == Some(nextFrameIndex)) { 211 // If we are not looping forever, initialize the loop counter 212 if (aState.mLoopRemainingCount < 0 && aState.LoopCount() >= 0) { 213 aState.mLoopRemainingCount = aState.LoopCount(); 214 } 215 216 // If animation mode is "loop once", or we're at end of loop counter, 217 // it's time to stop animating. 218 if (aState.mAnimationMode == imgIContainer::kLoopOnceAnimMode || 219 aState.mLoopRemainingCount == 0) { 220 ret.mAnimationFinished = true; 221 } 222 223 nextFrameIndex = 0; 224 225 if (aState.mLoopRemainingCount > 0) { 226 aState.mLoopRemainingCount--; 227 } 228 229 // If we're done, exit early. 230 if (ret.mAnimationFinished) { 231 return ret; 232 } 233 } 234 235 if (nextFrameIndex >= aState.KnownFrameCount()) { 236 // We've already advanced to the last decoded frame, nothing more we can do. 237 // We're blocked by network/decoding from displaying the animation at the 238 // rate specified, so that means the frame we are displaying (the latest 239 // available) is the frame we want to be displaying at this time. So we 240 // update the current animation time. If we didn't update the current 241 // animation time then it could lag behind, which would indicate that we are 242 // behind in the animation and should try to catch up. When we are done 243 // decoding (and thus can loop around back to the start of the animation) we 244 // would then jump to a random point in the animation to try to catch up. 245 // But we were never behind in the animation. 246 aState.mCurrentAnimationFrameTime = aTime; 247 return ret; 248 } 249 250 // There can be frames in the surface cache with index >= KnownFrameCount() 251 // which GetRawFrame() can access because an async decoder has decoded them, 252 // but which AnimationState doesn't know about yet because we haven't received 253 // the appropriate notification on the main thread. Make sure we stay in sync 254 // with AnimationState. 255 MOZ_ASSERT(nextFrameIndex < aState.KnownFrameCount()); 256 RefPtr<imgFrame> nextFrame = aFrames.GetFrame(nextFrameIndex); 257 258 // We should always check to see if we have the next frame even if we have 259 // previously finished decoding. If we needed to redecode (e.g. due to a draw 260 // failure) we would have discarded all the old frames and may not yet have 261 // the new ones. DrawableSurface::RawAccessRef promises to only return 262 // finished frames. 263 if (!nextFrame) { 264 // Uh oh, the frame we want to show is currently being decoded (partial). 265 // Similar to the above case, we could be blocked by network or decoding, 266 // and so we should advance our current time rather than risk jumping 267 // through the animation. We will wait until the next refresh driver tick 268 // and try again. 269 aState.mCurrentAnimationFrameTime = aTime; 270 return ret; 271 } 272 273 if (nextFrame->GetTimeout() == FrameTimeout::Forever()) { 274 ret.mAnimationFinished = true; 275 } 276 277 if (nextFrameIndex == 0) { 278 ret.mDirtyRect = aState.FirstFrameRefreshArea(); 279 } else { 280 ret.mDirtyRect = nextFrame->GetDirtyRect(); 281 } 282 283 aState.mCurrentAnimationFrameTime = 284 GetCurrentImgFrameEndTime(aState, aCurrentFrame->GetTimeout()); 285 286 // If we can get closer to the current time by a multiple of the image's loop 287 // time, we should. We can only do this if we're done decoding; otherwise, we 288 // don't know the full loop length, and LoopLength() will have to return 289 // FrameTimeout::Forever(). We also skip this for images with a finite loop 290 // count if we have initialized mLoopRemainingCount (it only gets initialized 291 // after one full loop). 292 FrameTimeout loopTime = aState.LoopLength(); 293 if (loopTime != FrameTimeout::Forever() && 294 (aState.LoopCount() < 0 || aState.mLoopRemainingCount >= 0)) { 295 TimeDuration delay = aTime - aState.mCurrentAnimationFrameTime; 296 if (delay.ToMilliseconds() > loopTime.AsMilliseconds()) { 297 // Explicitly use integer division to get the floor of the number of 298 // loops. 299 uint64_t loops = static_cast<uint64_t>(delay.ToMilliseconds()) / 300 loopTime.AsMilliseconds(); 301 302 // If we have a finite loop count limit the number of loops we advance. 303 if (aState.mLoopRemainingCount >= 0) { 304 MOZ_ASSERT(aState.LoopCount() >= 0); 305 loops = 306 std::min(loops, CheckedUint64(aState.mLoopRemainingCount).value()); 307 } 308 309 aState.mCurrentAnimationFrameTime += 310 TimeDuration::FromMilliseconds(loops * loopTime.AsMilliseconds()); 311 312 if (aState.mLoopRemainingCount >= 0) { 313 MOZ_ASSERT(loops <= CheckedUint64(aState.mLoopRemainingCount).value()); 314 aState.mLoopRemainingCount -= CheckedInt32(loops).value(); 315 } 316 } 317 } 318 319 // Set currentAnimationFrameIndex at the last possible moment 320 aState.mCurrentAnimationFrameIndex = nextFrameIndex; 321 aCurrentFrame = std::move(nextFrame); 322 aFrames.Advance(nextFrameIndex); 323 324 // If we're here, we successfully advanced the frame. 325 ret.mFrameAdvanced = true; 326 327 return ret; 328 } 329 330 void FrameAnimator::ResetAnimation(AnimationState& aState) { 331 aState.ResetAnimation(); 332 333 // Our surface provider is synchronized to our state, so we need to reset its 334 // state as well, if we still have one. 335 SurfaceCache::ResetAnimation( 336 ImageKey(mImage), 337 RasterSurfaceKey(mSize, DefaultSurfaceFlags(), PlaybackType::eAnimated)); 338 339 // Calling Reset on the surface of the animation can cause discarding surface 340 // providers to throw out all their frames so refresh our state. 341 OrientedIntRect rect = 342 OrientedIntRect::FromUnknownRect(aState.UpdateState(mImage, mSize)); 343 344 if (!rect.IsEmpty()) { 345 nsCOMPtr<nsIEventTarget> eventTarget = do_GetMainThread(); 346 RefPtr<RasterImage> image = mImage; 347 nsCOMPtr<nsIRunnable> ev = NS_NewRunnableFunction( 348 "FrameAnimator::ResetAnimation", 349 [=]() -> void { image->NotifyProgress(NoProgress, rect); }); 350 eventTarget->Dispatch(ev.forget(), NS_DISPATCH_NORMAL); 351 } 352 } 353 354 RefreshResult FrameAnimator::RequestRefresh(AnimationState& aState, 355 const TimeStamp& aTime) { 356 // By default, an empty RefreshResult. 357 RefreshResult ret; 358 359 if (aState.IsDiscarded()) { 360 aState.MaybeAdvanceAnimationFrameTime(aTime); 361 return ret; 362 } 363 364 // Get the animation frames once now, and pass them down to callees because 365 // the surface could be discarded at anytime on a different thread. This is 366 // must easier to reason about then trying to write code that is safe to 367 // having the surface disappear at anytime. 368 LookupResult result = SurfaceCache::Lookup( 369 ImageKey(mImage), 370 RasterSurfaceKey(mSize, DefaultSurfaceFlags(), PlaybackType::eAnimated), 371 /* aMarkUsed = */ true); 372 373 ret.mDirtyRect = aState.UpdateStateInternal(result, mSize); 374 if (aState.IsDiscarded() || !result) { 375 aState.MaybeAdvanceAnimationFrameTime(aTime); 376 return ret; 377 } 378 379 RefPtr<imgFrame> currentFrame = 380 result.Surface().GetFrame(aState.mCurrentAnimationFrameIndex); 381 382 // only advance the frame if the current time is greater than or 383 // equal to the current frame's end time. 384 if (!currentFrame) { 385 MOZ_ASSERT(StaticPrefs::image_mem_animated_discardable_AtStartup()); 386 MOZ_ASSERT(aState.GetHasRequestedDecode() && 387 !aState.GetIsCurrentlyDecoded()); 388 MOZ_ASSERT(aState.mCompositedFrameInvalid); 389 // Nothing we can do but wait for our previous current frame to be decoded 390 // again so we can determine what to do next. 391 aState.MaybeAdvanceAnimationFrameTime(aTime); 392 return ret; 393 } 394 395 TimeStamp currentFrameEndTime = 396 GetCurrentImgFrameEndTime(aState, currentFrame->GetTimeout()); 397 398 // If nothing has accessed the composited frame since the last time we 399 // advanced, then there is no point in continuing to advance the animation. 400 // This has the effect of freezing the animation while not in view. 401 if (!result.Surface().MayAdvance() && 402 aState.MaybeAdvanceAnimationFrameTime(aTime)) { 403 return ret; 404 } 405 406 while (currentFrameEndTime <= aTime) { 407 TimeStamp oldFrameEndTime = currentFrameEndTime; 408 409 RefreshResult frameRes = 410 AdvanceFrame(aState, result.Surface(), currentFrame, aTime); 411 412 // Accumulate our result for returning to callers. 413 ret.Accumulate(frameRes); 414 415 // currentFrame was updated by AdvanceFrame so it is still current. 416 currentFrameEndTime = 417 GetCurrentImgFrameEndTime(aState, currentFrame->GetTimeout()); 418 419 // If we didn't advance a frame, and our frame end time didn't change, 420 // then we need to break out of this loop & wait for the frame(s) 421 // to finish downloading. 422 if (!frameRes.mFrameAdvanced && currentFrameEndTime == oldFrameEndTime) { 423 break; 424 } 425 } 426 427 // We should only mark the composited frame as valid and reset the dirty rect 428 // if we advanced (meaning the next frame was actually produced somehow), the 429 // composited frame was previously invalid (so we may need to repaint 430 // everything) and either the frame index is valid (to know we were doing 431 // blending on the main thread, instead of on the decoder threads in advance), 432 // or the current frame is a full frame (blends off the main thread). 433 // 434 // If for some reason we forget to reset aState.mCompositedFrameInvalid, then 435 // GetCompositedFrame will fail, even if we have all the data available for 436 // display. 437 if (currentFrameEndTime > aTime && aState.mCompositedFrameInvalid) { 438 aState.mCompositedFrameInvalid = false; 439 ret.mDirtyRect = IntRect(IntPoint(0, 0), mSize); 440 } 441 442 MOZ_ASSERT(!aState.mIsCurrentlyDecoded || !aState.mCompositedFrameInvalid); 443 444 return ret; 445 } 446 447 LookupResult FrameAnimator::GetCompositedFrame(AnimationState& aState, 448 bool aMarkUsed) { 449 LookupResult result = SurfaceCache::Lookup( 450 ImageKey(mImage), 451 RasterSurfaceKey(mSize, DefaultSurfaceFlags(), PlaybackType::eAnimated), 452 aMarkUsed); 453 454 if (result) { 455 // If we are getting the frame directly (e.g. through tests or canvas), we 456 // need to ensure the animation is marked to allow advancing to the next 457 // frame. 458 result.Surface().MarkMayAdvance(); 459 } 460 461 if (aState.mCompositedFrameInvalid) { 462 MOZ_ASSERT(StaticPrefs::image_mem_animated_discardable_AtStartup()); 463 MOZ_ASSERT(aState.GetHasRequestedDecode()); 464 MOZ_ASSERT(!aState.GetIsCurrentlyDecoded()); 465 466 if (result.Type() == MatchType::EXACT) { 467 // If our composited frame is marked as invalid but our frames are in the 468 // surface cache we might just have not updated our internal state yet. 469 // This can happen if the image is not in a document so that 470 // RequestRefresh is not getting called to advance the frame. 471 // RequestRefresh would result in our composited frame getting marked as 472 // valid either at the end of RequestRefresh when we are able to advance 473 // to the current time or if advancing frames eventually causes us to 474 // decode all of the frames of the image resulting in DecodeComplete 475 // getting called which calls UpdateState. The reason we care about this 476 // is that img.decode promises won't resolve until GetCompositedFrame 477 // returns a frame. 478 OrientedIntRect rect = OrientedIntRect::FromUnknownRect( 479 aState.UpdateStateInternal(result, mSize)); 480 481 if (!rect.IsEmpty()) { 482 nsCOMPtr<nsIEventTarget> eventTarget = do_GetMainThread(); 483 RefPtr<RasterImage> image = mImage; 484 nsCOMPtr<nsIRunnable> ev = NS_NewRunnableFunction( 485 "FrameAnimator::GetCompositedFrame", 486 [=]() -> void { image->NotifyProgress(NoProgress, rect); }); 487 eventTarget->Dispatch(ev.forget(), NS_DISPATCH_NORMAL); 488 } 489 } 490 491 // If it's still invalid we have to return. 492 if (aState.mCompositedFrameInvalid) { 493 if (result.Type() == MatchType::NOT_FOUND) { 494 return result; 495 } 496 return LookupResult(MatchType::PENDING); 497 } 498 } 499 500 // Otherwise return the raw frame. DoBlend is required to ensure that we only 501 // hit this case if the frame is not paletted and doesn't require compositing. 502 if (!result) { 503 return result; 504 } 505 506 // Seek to the appropriate frame. If seeking fails, it means that we couldn't 507 // get the frame we're looking for; treat this as if the lookup failed. 508 if (NS_FAILED(result.Surface().Seek(aState.mCurrentAnimationFrameIndex))) { 509 if (result.Type() == MatchType::NOT_FOUND) { 510 return result; 511 } 512 return LookupResult(MatchType::PENDING); 513 } 514 515 return result; 516 } 517 518 } // namespace image 519 } // namespace mozilla