tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AnimationFrameBuffer.cpp (18461B)


      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 "AnimationFrameBuffer.h"
      7 
      8 #include <utility>  // for Move
      9 
     10 namespace mozilla {
     11 namespace image {
     12 
     13 AnimationFrameRetainedBuffer::AnimationFrameRetainedBuffer(size_t aThreshold,
     14                                                           size_t aBatch,
     15                                                           size_t aStartFrame)
     16    : AnimationFrameBuffer(aBatch, aStartFrame), mThreshold(aThreshold) {
     17  // To simplify the code, we have the assumption that the threshold for
     18  // entering discard-after-display mode is at least twice the batch size (since
     19  // that is the most frames-pending-decode we will request) + 1 for the current
     20  // frame. That way the redecoded frames being inserted will never risk
     21  // overlapping the frames we will discard due to the animation progressing.
     22  // That may cause us to use a little more memory than we want but that is an
     23  // acceptable tradeoff for simplicity.
     24  size_t minThreshold = 2 * mBatch + 1;
     25  if (mThreshold < minThreshold) {
     26    mThreshold = minThreshold;
     27  }
     28 
     29  // The maximum number of frames we should ever have decoded at one time is
     30  // twice the batch. That is a good as number as any to start our decoding at.
     31  mPending = mBatch * 2;
     32 }
     33 
     34 bool AnimationFrameRetainedBuffer::InsertInternal(RefPtr<imgFrame>&& aFrame) {
     35  // We should only insert new frames if we actually asked for them.
     36  MOZ_ASSERT(!mSizeKnown);
     37  MOZ_ASSERT(mFrames.Length() < mThreshold);
     38 
     39  ++mSize;
     40  mFrames.AppendElement(std::move(aFrame));
     41  MOZ_ASSERT(mSize == mFrames.Length());
     42  return mSize < mThreshold;
     43 }
     44 
     45 bool AnimationFrameRetainedBuffer::ResetInternal() {
     46  // If we haven't crossed the threshold, then we know by definition we have
     47  // not discarded any frames. If we previously requested more frames, but
     48  // it would have been more than we would have buffered otherwise, we can
     49  // stop the decoding after one more frame.
     50  if (mPending > 1 && mSize >= mBatch * 2 + 1) {
     51    MOZ_ASSERT(!mSizeKnown);
     52    mPending = 1;
     53  }
     54 
     55  // Either the decoder is still running, or we have enough frames already.
     56  // No need for us to restart it.
     57  return false;
     58 }
     59 
     60 bool AnimationFrameRetainedBuffer::MarkComplete(
     61    const gfx::IntRect& aFirstFrameRefreshArea) {
     62  MOZ_ASSERT(!mSizeKnown);
     63  mFirstFrameRefreshArea = aFirstFrameRefreshArea;
     64  mSizeKnown = true;
     65  mPending = 0;
     66  mFrames.Compact();
     67  return false;
     68 }
     69 
     70 void AnimationFrameRetainedBuffer::AdvanceInternal() {
     71  // We should not have advanced if we never inserted.
     72  MOZ_ASSERT(!mFrames.IsEmpty());
     73  // We only want to change the current frame index if we have advanced. This
     74  // means either a higher frame index, or going back to the beginning.
     75  size_t framesLength = mFrames.Length();
     76  // We should never have advanced beyond the frame buffer.
     77  MOZ_ASSERT(mGetIndex < framesLength);
     78  // We should never advance if the current frame is null -- it needs to know
     79  // the timeout from it at least to know when to advance.
     80  MOZ_ASSERT_IF(mGetIndex > 0, mFrames[mGetIndex - 1]);
     81  MOZ_ASSERT_IF(mGetIndex == 0, mFrames[framesLength - 1]);
     82  // The owner should have already accessed the next frame, so it should also
     83  // be available.
     84  MOZ_ASSERT(mFrames[mGetIndex]);
     85 
     86  if (!mSizeKnown) {
     87    // Calculate how many frames we have requested ahead of the current frame.
     88    size_t buffered = mPending + framesLength - mGetIndex - 1;
     89    if (buffered < mBatch) {
     90      // If we have fewer frames than the batch size, then ask for more. If we
     91      // do not have any pending, then we know that there is no active decoding.
     92      mPending += mBatch;
     93    }
     94  }
     95 }
     96 
     97 imgFrame* AnimationFrameRetainedBuffer::Get(size_t aFrame, bool aForDisplay) {
     98  // We should not have asked for a frame if we never inserted.
     99  if (mFrames.IsEmpty()) {
    100    MOZ_ASSERT_UNREACHABLE("Calling Get() when we have no frames");
    101    return nullptr;
    102  }
    103 
    104  // If we don't have that frame, return an empty frame ref.
    105  if (aFrame >= mFrames.Length()) {
    106    return nullptr;
    107  }
    108 
    109  // If we have space for the frame, it should always be available.
    110  if (!mFrames[aFrame]) {
    111    MOZ_ASSERT_UNREACHABLE("Calling Get() when frame is unavailable");
    112    return nullptr;
    113  }
    114 
    115  // If we are advancing on behalf of the animation, we don't expect it to be
    116  // getting any frames (besides the first) until we get the desired frame.
    117  MOZ_ASSERT(aFrame == 0 || mAdvance == 0);
    118  return mFrames[aFrame].get();
    119 }
    120 
    121 bool AnimationFrameRetainedBuffer::IsFirstFrameFinished() const {
    122  return !mFrames.IsEmpty() && mFrames[0]->IsFinished();
    123 }
    124 
    125 bool AnimationFrameRetainedBuffer::IsLastInsertedFrame(imgFrame* aFrame) const {
    126  return !mFrames.IsEmpty() && mFrames.LastElement().get() == aFrame;
    127 }
    128 
    129 void AnimationFrameRetainedBuffer::AddSizeOfExcludingThis(
    130    MallocSizeOf aMallocSizeOf, const AddSizeOfCb& aCallback) {
    131  size_t i = 0;
    132  for (const RefPtr<imgFrame>& frame : mFrames) {
    133    ++i;
    134    frame->AddSizeOfExcludingThis(aMallocSizeOf,
    135                                  [&](AddSizeOfCbData& aMetadata) {
    136                                    aMetadata.mIndex = i;
    137                                    aCallback(aMetadata);
    138                                  });
    139  }
    140 }
    141 
    142 AnimationFrameDiscardingQueue::AnimationFrameDiscardingQueue(
    143    AnimationFrameRetainedBuffer&& aQueue)
    144    : AnimationFrameBuffer(aQueue),
    145      mInsertIndex(aQueue.mFrames.Length()),
    146      mFirstFrame(aQueue.mFrames[0]) {
    147  MOZ_ASSERT(!mSizeKnown);
    148  MOZ_ASSERT(!mRedecodeError);
    149  MOZ_ASSERT(mInsertIndex > 0);
    150  mMayDiscard = true;
    151 
    152  // We avoided moving aQueue.mFrames[0] for mFirstFrame above because it is
    153  // possible the animation was reset back to the beginning, and then we crossed
    154  // the threshold without advancing further. That would mean mGetIndex is 0.
    155  for (size_t i = mGetIndex; i < mInsertIndex; ++i) {
    156    MOZ_ASSERT(aQueue.mFrames[i]);
    157    mDisplay.push_back(std::move(aQueue.mFrames[i]));
    158  }
    159 }
    160 
    161 bool AnimationFrameDiscardingQueue::InsertInternal(RefPtr<imgFrame>&& aFrame) {
    162  if (mInsertIndex == mSize) {
    163    if (mSizeKnown) {
    164      // We produced more frames on a subsequent decode than on the first pass.
    165      mRedecodeError = true;
    166      mPending = 0;
    167      return true;
    168    }
    169    ++mSize;
    170  }
    171 
    172  // Even though we don't use redecoded first frames for display purposes, we
    173  // will still use them for recycling, so we still need to insert it.
    174  mDisplay.push_back(std::move(aFrame));
    175  ++mInsertIndex;
    176  MOZ_ASSERT(mInsertIndex <= mSize);
    177  return true;
    178 }
    179 
    180 bool AnimationFrameDiscardingQueue::ResetInternal() {
    181  mDisplay.clear();
    182  mInsertIndex = 0;
    183 
    184  bool restartDecoder = mPending == 0;
    185  mPending = 2 * mBatch;
    186  return restartDecoder;
    187 }
    188 
    189 bool AnimationFrameDiscardingQueue::MarkComplete(
    190    const gfx::IntRect& aFirstFrameRefreshArea) {
    191  if (NS_WARN_IF(mInsertIndex != mSize)) {
    192    mRedecodeError = true;
    193    mPending = 0;
    194  }
    195 
    196  // If we encounter a redecode error, just make the first frame refresh area to
    197  // be the full frame, because we don't really know what we can safely recycle.
    198  mFirstFrameRefreshArea =
    199      mRedecodeError ? mFirstFrame->GetRect() : aFirstFrameRefreshArea;
    200 
    201  // We reached the end of the animation, the next frame we get, if we get
    202  // another, will be the first frame again.
    203  mInsertIndex = 0;
    204  mSizeKnown = true;
    205 
    206  // Since we only request advancing when we want to resume at a certain point
    207  // in the animation, we should never exceed the number of frames.
    208  MOZ_ASSERT(mAdvance == 0);
    209  return mPending > 0;
    210 }
    211 
    212 void AnimationFrameDiscardingQueue::AdvanceInternal() {
    213  // We only want to change the current frame index if we have advanced. This
    214  // means either a higher frame index, or going back to the beginning.
    215  // We should never have advanced beyond the frame buffer.
    216  MOZ_ASSERT(mGetIndex < mSize);
    217 
    218  // We should have the current frame still in the display queue. Either way,
    219  // we should at least have an entry in the queue which we need to consume.
    220  MOZ_ASSERT(!mDisplay.empty());
    221  MOZ_ASSERT(mDisplay.front());
    222  mDisplay.pop_front();
    223  MOZ_ASSERT(!mDisplay.empty());
    224  MOZ_ASSERT(mDisplay.front());
    225 
    226  if (mDisplay.size() + mPending - 1 < mBatch) {
    227    // If we have fewer frames than the batch size, then ask for more. If we
    228    // do not have any pending, then we know that there is no active decoding.
    229    mPending += mBatch;
    230  }
    231 }
    232 
    233 imgFrame* AnimationFrameDiscardingQueue::Get(size_t aFrame, bool aForDisplay) {
    234  // The first frame is stored separately. If we only need the frame for
    235  // display purposes, we can return it right away. If we need it for advancing
    236  // the animation, we want to verify the recreated first frame is available
    237  // before allowing it continue.
    238  if (aForDisplay && aFrame == 0) {
    239    return mFirstFrame.get();
    240  }
    241 
    242  // If we don't have that frame, return an empty frame ref.
    243  if (aFrame >= mSize) {
    244    return nullptr;
    245  }
    246 
    247  size_t offset;
    248  if (aFrame >= mGetIndex) {
    249    offset = aFrame - mGetIndex;
    250  } else if (!mSizeKnown) {
    251    MOZ_ASSERT_UNREACHABLE("Requesting previous frame after we have advanced!");
    252    return nullptr;
    253  } else {
    254    offset = mSize - mGetIndex + aFrame;
    255  }
    256 
    257  if (offset >= mDisplay.size()) {
    258    return nullptr;
    259  }
    260 
    261  // If we are advancing on behalf of the animation, we don't expect it to be
    262  // getting any frames (besides the first) until we get the desired frame.
    263  MOZ_ASSERT(aFrame == 0 || mAdvance == 0);
    264 
    265  // If we have space for the frame, it should always be available.
    266  MOZ_ASSERT(mDisplay[offset]);
    267  return mDisplay[offset].get();
    268 }
    269 
    270 bool AnimationFrameDiscardingQueue::IsFirstFrameFinished() const {
    271  MOZ_ASSERT(mFirstFrame);
    272  MOZ_ASSERT(mFirstFrame->IsFinished());
    273  return true;
    274 }
    275 
    276 bool AnimationFrameDiscardingQueue::IsLastInsertedFrame(
    277    imgFrame* aFrame) const {
    278  return !mDisplay.empty() && mDisplay.back().get() == aFrame;
    279 }
    280 
    281 void AnimationFrameDiscardingQueue::AddSizeOfExcludingThis(
    282    MallocSizeOf aMallocSizeOf, const AddSizeOfCb& aCallback) {
    283  mFirstFrame->AddSizeOfExcludingThis(aMallocSizeOf,
    284                                      [&](AddSizeOfCbData& aMetadata) {
    285                                        aMetadata.mIndex = 1;
    286                                        aCallback(aMetadata);
    287                                      });
    288 
    289  size_t i = mGetIndex;
    290  for (const RefPtr<imgFrame>& frame : mDisplay) {
    291    ++i;
    292    if (mSize < i) {
    293      i = 1;
    294      if (mFirstFrame.get() == frame.get()) {
    295        // First frame again, we already covered it above. We can have a
    296        // different frame in the first frame position in the discard queue
    297        // on subsequent passes of the animation. This is useful for recycling.
    298        continue;
    299      }
    300    }
    301 
    302    frame->AddSizeOfExcludingThis(aMallocSizeOf,
    303                                  [&](AddSizeOfCbData& aMetadata) {
    304                                    aMetadata.mIndex = i;
    305                                    aCallback(aMetadata);
    306                                  });
    307  }
    308 }
    309 
    310 AnimationFrameRecyclingQueue::AnimationFrameRecyclingQueue(
    311    AnimationFrameRetainedBuffer&& aQueue)
    312    : AnimationFrameDiscardingQueue(std::move(aQueue)),
    313      mForceUseFirstFrameRefreshArea(false) {
    314  // In an ideal world, we would always save the already displayed frames for
    315  // recycling but none of the frames were marked as recyclable. We will incur
    316  // the extra allocation cost for a few more frames.
    317  mRecycling = true;
    318 
    319  // Until we reach the end of the animation, set the first frame refresh area
    320  // to match that of the full area of the first frame.
    321  mFirstFrameRefreshArea = mFirstFrame->GetRect();
    322 }
    323 
    324 void AnimationFrameRecyclingQueue::AddSizeOfExcludingThis(
    325    MallocSizeOf aMallocSizeOf, const AddSizeOfCb& aCallback) {
    326  AnimationFrameDiscardingQueue::AddSizeOfExcludingThis(aMallocSizeOf,
    327                                                        aCallback);
    328 
    329  for (const RecycleEntry& entry : mRecycle) {
    330    if (entry.mFrame) {
    331      entry.mFrame->AddSizeOfExcludingThis(
    332          aMallocSizeOf, [&](AddSizeOfCbData& aMetadata) {
    333            aMetadata.mIndex = 0;  // Frame is not applicable
    334            aCallback(aMetadata);
    335          });
    336    }
    337  }
    338 }
    339 
    340 void AnimationFrameRecyclingQueue::AdvanceInternal() {
    341  // We only want to change the current frame index if we have advanced. This
    342  // means either a higher frame index, or going back to the beginning.
    343  // We should never have advanced beyond the frame buffer.
    344  MOZ_ASSERT(mGetIndex < mSize);
    345 
    346  MOZ_ASSERT(!mDisplay.empty());
    347  MOZ_ASSERT(mDisplay.front());
    348 
    349  // We have advanced past the first frame. That means the next frame we are
    350  // putting in the queue to recycling is the first frame in the animation,
    351  // and we no longer need to worry about having looped around.
    352  if (mGetIndex == 1) {
    353    mForceUseFirstFrameRefreshArea = false;
    354  }
    355 
    356  RefPtr<imgFrame>& front = mDisplay.front();
    357  RecycleEntry newEntry(mForceUseFirstFrameRefreshArea ? mFirstFrameRefreshArea
    358                                                       : front->GetDirtyRect());
    359 
    360  // If we are allowed to recycle the frame, then we should save it before the
    361  // base class's AdvanceInternal discards it.
    362  newEntry.mFrame = std::move(front);
    363 
    364  // Even if the frame itself isn't saved, we want the dirty rect to calculate
    365  // the recycle rect for future recycled frames.
    366  mRecycle.push_back(std::move(newEntry));
    367  mDisplay.pop_front();
    368  MOZ_ASSERT(!mDisplay.empty());
    369  MOZ_ASSERT(mDisplay.front());
    370 
    371  if (mDisplay.size() + mPending - 1 < mBatch) {
    372    // If we have fewer frames than the batch size, then ask for more. If we
    373    // do not have any pending, then we know that there is no active decoding.
    374    //
    375    // We limit the batch to avoid using the frame we just added to the queue.
    376    // This gives other parts of the system time to switch to the new current
    377    // frame, and maximize buffer reuse. In particular this is useful for
    378    // WebRender which holds onto the previous frame for much longer.
    379    size_t newPending = std::min(mPending + mBatch, mRecycle.size() - 1);
    380    if (newPending == 0 && (mDisplay.size() <= 1 || mPending > 0)) {
    381      // If we already have pending frames, then the decoder is active and we
    382      // cannot go below one. If we are displaying the only frame we have, and
    383      // there are none pending, then we must request at least one more frame to
    384      // continue to animation, because we won't advance again without a new
    385      // frame. This may cause us to skip recycling because the previous frame
    386      // is still in use.
    387      newPending = 1;
    388    }
    389    mPending = newPending;
    390  }
    391 }
    392 
    393 bool AnimationFrameRecyclingQueue::ResetInternal() {
    394  // We should save any display frames that we can to save on at least the
    395  // allocation. The first frame refresh area is guaranteed to be the aggregate
    396  // dirty rect or the entire frame, and so the bare minimum area we can
    397  // recycle. We don't need to worry about updating the dirty rect for the
    398  // existing mRecycle entries, because that will happen in RecycleFrame when
    399  // we try to pull out a frame to redecode the first frame.
    400  for (RefPtr<imgFrame>& frame : mDisplay) {
    401    RecycleEntry newEntry(mFirstFrameRefreshArea);
    402    newEntry.mFrame = std::move(frame);
    403    mRecycle.push_back(std::move(newEntry));
    404  }
    405 
    406  return AnimationFrameDiscardingQueue::ResetInternal();
    407 }
    408 
    409 RawAccessFrameRef AnimationFrameRecyclingQueue::RecycleFrame(
    410    gfx::IntRect& aRecycleRect) {
    411  if (mInsertIndex == 0) {
    412    // If we are recreating the first frame, then we actually have already
    413    // precomputed aggregate of the dirty rects as the first frame refresh
    414    // area. We know that all of the frames still in the recycling queue
    415    // need to take into account the same dirty rect because they are also
    416    // frames which cross the boundary.
    417    //
    418    // Note that this may actually shrink the dirty rect if we estimated it
    419    // earlier with the full frame size and now we have the actual, more
    420    // conservative aggregate for the animation.
    421    for (RecycleEntry& entry : mRecycle) {
    422      entry.mDirtyRect = mFirstFrameRefreshArea;
    423    }
    424    // Until we advance to the first frame again, any subsequent recycled
    425    // frames should also use the first frame refresh area.
    426    mForceUseFirstFrameRefreshArea = true;
    427  }
    428 
    429  if (mRecycle.empty()) {
    430    return RawAccessFrameRef();
    431  }
    432 
    433  RawAccessFrameRef recycledFrame;
    434  if (mRecycle.front().mFrame) {
    435    recycledFrame = mRecycle.front().mFrame->RawAccessRef(
    436        gfx::DataSourceSurface::READ_WRITE);
    437    mRecycle.pop_front();
    438 
    439    // If we couldn't map in the surface, it is probably because the frame was
    440    // finalized and we did not expect to need to write into it again. This
    441    // happens for the first frames produced during an animation.
    442    if (recycledFrame) {
    443      if (mForceUseFirstFrameRefreshArea) {
    444        // We are still crossing the loop boundary and cannot rely upon the
    445        // dirty rects of entries in mDisplay to be representative. E.g. The
    446        // first frame is probably has a full frame dirty rect.
    447        aRecycleRect = mFirstFrameRefreshArea;
    448      } else {
    449        // Calculate the recycle rect for the recycled frame. This is the
    450        // cumulative dirty rect of all of the frames ahead of us to be
    451        // displayed, and to be used for recycling. Or in other words, the dirty
    452        // rect between the recycled frame and the decoded frame which reuses
    453        // the buffer.
    454        //
    455        // We know at this point that mRecycle contains either frames from the
    456        // end of the animation with the first frame refresh area as the dirty
    457        // rect (plus the first frame likewise) and frames with their actual
    458        // dirty rect from the start. mDisplay should also only contain frames
    459        // from the start of the animation onwards.
    460        aRecycleRect.SetRect(0, 0, 0, 0);
    461        for (const RefPtr<imgFrame>& frame : mDisplay) {
    462          aRecycleRect = aRecycleRect.Union(frame->GetDirtyRect());
    463        }
    464        for (const RecycleEntry& entry : mRecycle) {
    465          aRecycleRect = aRecycleRect.Union(entry.mDirtyRect);
    466        }
    467      }
    468    }
    469  } else {
    470    mRecycle.pop_front();
    471  }
    472 
    473  return recycledFrame;
    474 }
    475 
    476 }  // namespace image
    477 }  // namespace mozilla