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