Ejemplo n.º 1
0
RefreshResult
FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
{
  NS_ASSERTION(aTime <= TimeStamp::Now(),
               "Given time appears to be in the future");
  PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);

  RefreshResult ret;

  // Determine what the next frame is, taking into account looping.
  uint32_t currentFrameIndex = aState.mCurrentAnimationFrameIndex;
  uint32_t nextFrameIndex = currentFrameIndex + 1;

  // Check if we're at the end of the loop. (FrameCount() returns Nothing() if
  // we don't know the total count yet.)
  if (aState.FrameCount() == Some(nextFrameIndex)) {
    // If we are not looping forever, initialize the loop counter
    if (aState.mLoopRemainingCount < 0 && aState.LoopCount() >= 0) {
      aState.mLoopRemainingCount = aState.LoopCount();
    }

    // If animation mode is "loop once", or we're at end of loop counter,
    // it's time to stop animating.
    if (aState.mAnimationMode == imgIContainer::kLoopOnceAnimMode ||
        aState.mLoopRemainingCount == 0) {
      ret.mAnimationFinished = true;
    }

    nextFrameIndex = 0;

    if (aState.mLoopRemainingCount > 0) {
      aState.mLoopRemainingCount--;
    }

    // If we're done, exit early.
    if (ret.mAnimationFinished) {
      return ret;
    }
  }

  if (nextFrameIndex >= aState.KnownFrameCount()) {
    // We've already advanced to the last decoded frame, nothing more we can do.
    // We're blocked by network/decoding from displaying the animation at the
    // rate specified, so that means the frame we are displaying (the latest
    // available) is the frame we want to be displaying at this time. So we
    // update the current animation time. If we didn't update the current
    // animation time then it could lag behind, which would indicate that we are
    // behind in the animation and should try to catch up. When we are done
    // decoding (and thus can loop around back to the start of the animation) we
    // would then jump to a random point in the animation to try to catch up.
    // But we were never behind in the animation.
    aState.mCurrentAnimationFrameTime = aTime;
    return ret;
  }

  // There can be frames in the surface cache with index >= KnownFrameCount()
  // which GetRawFrame() can access because an async decoder has decoded them,
  // but which AnimationState doesn't know about yet because we haven't received
  // the appropriate notification on the main thread. Make sure we stay in sync
  // with AnimationState.
  MOZ_ASSERT(nextFrameIndex < aState.KnownFrameCount());
  RawAccessFrameRef nextFrame = GetRawFrame(nextFrameIndex);

  // We should always check to see if we have the next frame even if we have
  // previously finished decoding. If we needed to redecode (e.g. due to a draw
  // failure) we would have discarded all the old frames and may not yet have
  // the new ones.
  if (!nextFrame || !nextFrame->IsFinished()) {
    // Uh oh, the frame we want to show is currently being decoded (partial)
    // Wait until the next refresh driver tick and try again
    return ret;
  }

  if (GetTimeoutForFrame(nextFrameIndex) == FrameTimeout::Forever()) {
    ret.mAnimationFinished = true;
  }

  if (nextFrameIndex == 0) {
    ret.mDirtyRect = aState.FirstFrameRefreshArea();
  } else {
    MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1);

    // Change frame
    if (!DoBlend(&ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) {
      // something went wrong, move on to next
      NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
      nextFrame->SetCompositingFailed(true);
      aState.mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(aState);
      aState.mCurrentAnimationFrameIndex = nextFrameIndex;

      return ret;
    }

    nextFrame->SetCompositingFailed(false);
  }

  aState.mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(aState);

  // If we can get closer to the current time by a multiple of the image's loop
  // time, we should. We can only do this if we're done decoding; otherwise, we
  // don't know the full loop length, and LoopLength() will have to return
  // FrameTimeout::Forever().
  FrameTimeout loopTime = aState.LoopLength();
  if (loopTime != FrameTimeout::Forever()) {
    TimeDuration delay = aTime - aState.mCurrentAnimationFrameTime;
    if (delay.ToMilliseconds() > loopTime.AsMilliseconds()) {
      // Explicitly use integer division to get the floor of the number of
      // loops.
      uint64_t loops = static_cast<uint64_t>(delay.ToMilliseconds())
                     / loopTime.AsMilliseconds();
      aState.mCurrentAnimationFrameTime +=
        TimeDuration::FromMilliseconds(loops * loopTime.AsMilliseconds());
    }
  }

  // Set currentAnimationFrameIndex at the last possible moment
  aState.mCurrentAnimationFrameIndex = nextFrameIndex;

  // If we're here, we successfully advanced the frame.
  ret.mFrameAdvanced = true;

  return ret;
}
Ejemplo n.º 2
0
FrameAnimator::RefreshResult
FrameAnimator::AdvanceFrame(TimeStamp aTime)
{
  NS_ASSERTION(aTime <= TimeStamp::Now(),
               "Given time appears to be in the future");
  PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);

  RefreshResult ret;

  // Determine what the next frame is, taking into account looping.
  uint32_t currentFrameIndex = mCurrentAnimationFrameIndex;
  uint32_t nextFrameIndex = currentFrameIndex + 1;

  if (mImage->GetNumFrames() == nextFrameIndex) {
    // We can only accurately determine if we are at the end of the loop if we are
    // done decoding, otherwise we don't know how many frames there will be.
    if (!mDoneDecoding) {
      // We've already advanced to the last decoded frame, nothing more we can do.
      // We're blocked by network/decoding from displaying the animation at the
      // rate specified, so that means the frame we are displaying (the latest
      // available) is the frame we want to be displaying at this time. So we
      // update the current animation time. If we didn't update the current
      // animation time then it could lag behind, which would indicate that we
      // are behind in the animation and should try to catch up. When we are
      // done decoding (and thus can loop around back to the start of the
      // animation) we would then jump to a random point in the animation to
      // try to catch up. But we were never behind in the animation.
      mCurrentAnimationFrameTime = aTime;
      return ret;
    }

    // End of an animation loop...

    // If we are not looping forever, initialize the loop counter
    if (mLoopRemainingCount < 0 && LoopCount() >= 0) {
      mLoopRemainingCount = LoopCount();
    }

    // If animation mode is "loop once", or we're at end of loop counter,
    // it's time to stop animating.
    if (mAnimationMode == imgIContainer::kLoopOnceAnimMode ||
        mLoopRemainingCount == 0) {
      ret.animationFinished = true;
    }

    nextFrameIndex = 0;

    if (mLoopRemainingCount > 0) {
      mLoopRemainingCount--;
    }

    // If we're done, exit early.
    if (ret.animationFinished) {
      return ret;
    }
  }

  // There can be frames in the surface cache with index >= mImage->GetNumFrames()
  // that GetRawFrame can access because the decoding thread has decoded them, but
  // RasterImage hasn't acknowledged those frames yet. We don't want to go past
  // what RasterImage knows about so that we stay in sync with RasterImage. The code
  // above should obey this, the MOZ_ASSERT records this invariant.
  MOZ_ASSERT(nextFrameIndex < mImage->GetNumFrames());
  RawAccessFrameRef nextFrame = GetRawFrame(nextFrameIndex);

  // If we're done decoding, we know we've got everything we're going to get.
  // If we aren't, we only display fully-downloaded frames; everything else
  // gets delayed.
  bool canDisplay = mDoneDecoding ||
                    (nextFrame && nextFrame->IsFinished());

  if (!canDisplay) {
    // Uh oh, the frame we want to show is currently being decoded (partial)
    // Wait until the next refresh driver tick and try again
    return ret;
  }

  // Bad data
  if (GetTimeoutForFrame(nextFrameIndex) < 0) {
    ret.animationFinished = true;
    ret.error = true;
  }

  if (nextFrameIndex == 0) {
    ret.dirtyRect = mFirstFrameRefreshArea;
  } else {
    MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1);

    // Change frame
    if (!DoBlend(&ret.dirtyRect, currentFrameIndex, nextFrameIndex)) {
      // something went wrong, move on to next
      NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
      nextFrame->SetCompositingFailed(true);
      mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime();
      mCurrentAnimationFrameIndex = nextFrameIndex;

      ret.error = true;
      return ret;
    }

    nextFrame->SetCompositingFailed(false);
  }

  mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime();

  // If we can get closer to the current time by a multiple of the image's loop
  // time, we should. We need to be done decoding in order to know the full loop
  // time though!
  int32_t loopTime = GetSingleLoopTime();
  if (loopTime > 0) {
    // We shouldn't be advancing by a whole loop unless we are decoded and know
    // what a full loop actually is. GetSingleLoopTime should return -1 so this
    // never happens.
    MOZ_ASSERT(mDoneDecoding);
    TimeDuration delay = aTime - mCurrentAnimationFrameTime;
    if (delay.ToMilliseconds() > loopTime) {
      // Explicitly use integer division to get the floor of the number of
      // loops.
      uint64_t loops = static_cast<uint64_t>(delay.ToMilliseconds()) / loopTime;
      mCurrentAnimationFrameTime +=
        TimeDuration::FromMilliseconds(loops * loopTime);
    }
  }

  // Set currentAnimationFrameIndex at the last possible moment
  mCurrentAnimationFrameIndex = nextFrameIndex;

  // If we're here, we successfully advanced the frame.
  ret.frameAdvanced = true;

  return ret;
}