/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Calculate how many frames we need to decode in this animation before we // enter decode-on-demand mode.
IntSize frameSize = aSurfaceKey.Size();
size_t threshold =
(size_t(StaticPrefs::image_animated_decode_on_demand_threshold_kb()) *
1024) /
(sizeof(uint32_t) * frameSize.width * frameSize.height);
size_t batch = StaticPrefs::image_animated_decode_on_demand_batch_size();
mFrames.reset( new AnimationFrameRetainedBuffer(threshold, batch, aCurrentFrame));
}
mSharedAnimation->Destroy(); if (mDecoder) {
mDecoder->SetFrameRecycler(nullptr);
}
}
void AnimationSurfaceProvider::DropImageReference() { if (!mImage) { return; // Nothing to do.
}
// RasterImage objects need to be destroyed on the main thread.
SurfaceCache::ReleaseImageOnMainThread(mImage.forget());
}
void AnimationSurfaceProvider::Reset() { // We want to go back to the beginning. bool mayDiscard; bool restartDecoder = false;
{
MutexAutoLock lock(mFramesMutex);
// If we have not crossed the threshold, we know we haven't discarded any // frames, and thus we know it is safe move our display index back to the // very beginning. It would be cleaner to let the frame buffer make this // decision inside the AnimationFrameBuffer::Reset method, but if we have // crossed the threshold, we need to hold onto the decoding mutex too. We // should avoid blocking the main thread on the decoder threads.
mayDiscard = mFrames->MayDiscard(); if (!mayDiscard) {
restartDecoder = mFrames->Reset();
}
}
if (mayDiscard) { // We are over the threshold and have started discarding old frames. In // that case we need to seize the decoding mutex. Thankfully we know that // we are in the process of decoding at most the batch size frames, so // this should not take too long to acquire.
MutexAutoLock lock(mDecodingMutex);
// We may have hit an error while redecoding. Because FrameAnimator is // tightly coupled to our own state, that means we would need to go through // some heroics to resume animating in those cases. The typical reason for // a redecode to fail is out of memory, and recycling should prevent most of // those errors. When image.animated.generate-full-frames has shipped // enabled on a release or two, we can simply remove the old FrameAnimator // blending code and simplify this quite a bit -- just always pop the next // full frame and timeout off the stack. if (mDecoder) {
mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder);
MOZ_ASSERT(mDecoder);
size_t AnimationSurfaceProvider::LogicalSizeInBytes() const { // When decoding animated images, we need at most three live surfaces: the // composited surface, the previous composited surface for // DisposalMethod::RESTORE_PREVIOUS, and the surface we're currently decoding // into. The composited surfaces are always BGRA. Although the surface we're // decoding into may be paletted, and may be smaller than the real size of the // image, we assume the worst case here. // XXX(seth): Note that this is actually not accurate yet; we're storing the // full sequence of frames, not just the three live surfaces mentioned above. // Unfortunately there's no way to know in advance how many frames an // animation has, so we really can't do better here. This will become correct // once bug 1289954 is complete.
IntSize size = GetSurfaceKey().Size(); return 3 * size.width * size.height * sizeof(uint32_t);
}
void AnimationSurfaceProvider::AddSizeOfExcludingThis(
MallocSizeOf aMallocSizeOf, const AddSizeOfCb& aCallback) { // Note that the surface cache lock is already held here, and then we acquire // mFramesMutex. For this method, this ordering is unavoidable, which means // that we must be careful to always use the same ordering elsewhere.
MutexAutoLock lock(mFramesMutex);
mFrames->AddSizeOfExcludingThis(aMallocSizeOf, aCallback);
}
if (!mDecoder) {
MOZ_ASSERT_UNREACHABLE("Running after decoding finished?"); return;
}
while (true) { // Run the decoder.
LexerResult result = mDecoder->Decode(WrapNotNull(this));
if (result.is<TerminalState>()) { // We may have a new frame now, but it's not guaranteed - a decoding // failure or truncated data may mean that no new frame got produced. // Since we're not sure, rather than call CheckForNewFrameAtYield() here // we call CheckForNewFrameAtTerminalState(), which handles both of these // possibilities. bool continueDecoding = CheckForNewFrameAtTerminalState();
FinishDecoding();
// Even if it is the last frame, we may not have enough frames buffered // ahead of the current. If we are shutting down, we want to ensure we // release the thread as soon as possible. The animation may advance even // during shutdown, which keeps us decoding, and thus blocking the decode // pool during teardown. if (!mDecoder || !continueDecoding || DecodePool::IsShuttingDown()) { return;
}
// Restart from the very beginning because the decoder was recreated. continue;
}
// If there is output available we want to change the entry in the surface // cache from a placeholder to an actual surface now before NotifyProgress // call below so that when consumers get the frame complete notification // from the NotifyProgress they can actually get a surface from the surface // cache. bool checkForNewFrameAtYieldResult = false; if (result == LexerResult(Yield::OUTPUT_AVAILABLE)) {
checkForNewFrameAtYieldResult = CheckForNewFrameAtYield();
}
// Notify for the progress we've made so far. if (mImage && mDecoder->HasProgress()) {
NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder));
}
if (result == LexerResult(Yield::NEED_MORE_DATA)) { // We can't make any more progress right now. The decoder itself will // ensure that we get reenqueued when more data is available; just return // for now. return;
}
// There's new output available - a new frame! Grab it. If we don't need any // more for the moment we can break out of the loop. If we are shutting // down, we want to ensure we release the thread as soon as possible. The // animation may advance even during shutdown, which keeps us decoding, and // thus blocking the decode pool during teardown.
MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE)); if (!checkForNewFrameAtYieldResult || DecodePool::IsShuttingDown()) { return;
}
}
}
// Try to get the new frame from the decoder.
RefPtr<imgFrame> frame = mDecoder->GetCurrentFrame();
MOZ_ASSERT(mDecoder->HasFrameToTake());
mDecoder->ClearHasFrameToTake();
if (!frame) {
MOZ_ASSERT_UNREACHABLE("Decoder yielded but didn't produce a frame?"); returntrue;
}
// We should've gotten a different frame than last time.
MOZ_ASSERT(!mFrames->IsLastInsertedFrame(frame));
// Append the new frame to the list.
AnimationFrameBuffer::InsertStatus status =
mFrames->Insert(std::move(frame));
// If we hit a redecode error, then we actually want to stop. This happens // when we tried to insert more frames than we originally had (e.g. the // original decoder attempt hit an OOM error sooner than we did). Better to // stop the animation than to get out of sync with FrameAnimator. if (mFrames->HasRedecodeError()) {
mDecoder = nullptr; returnfalse;
}
switch (status) { case AnimationFrameBuffer::InsertStatus::DISCARD_CONTINUE:
continueDecoding = true;
[[fallthrough]]; case AnimationFrameBuffer::InsertStatus::DISCARD_YIELD:
RequestFrameDiscarding(); break; case AnimationFrameBuffer::InsertStatus::CONTINUE:
continueDecoding = true; break; case AnimationFrameBuffer::InsertStatus::YIELD: break; default:
MOZ_ASSERT_UNREACHABLE("Unhandled insert status!"); break;
}
// We only want to handle the first frame if it is the first pass for the // animation decoder. The owning image will be cleared after that.
size_t frameCount = mFrames->Size(); if (frameCount == 1 && mImage) {
justGotFirstFrame = true;
}
}
if (justGotFirstFrame) {
AnnounceSurfaceAvailable();
}
// The decoder may or may not have a new frame for us at this point. Avoid // reinserting the same frame again.
RefPtr<imgFrame> frame = mDecoder->GetCurrentFrame();
// If the decoder didn't finish a new frame (ie if, after starting the // frame, it got an error and aborted the frame and the rest of the decode) // that means it won't be reporting it to the image or FrameAnimator so we // should ignore it too, that's what HasFrameToTake tracks basically. if (!mDecoder->HasFrameToTake()) {
frame = nullptr;
} else {
MOZ_ASSERT(frame);
mDecoder->ClearHasFrameToTake();
}
if (!frame || mFrames->IsLastInsertedFrame(frame)) { return mFrames->MarkComplete(mDecoder->GetFirstFrameRefreshArea());
}
// Append the new frame to the list.
AnimationFrameBuffer::InsertStatus status =
mFrames->Insert(std::move(frame));
// If we hit a redecode error, then we actually want to stop. This will be // fully handled in FinishDecoding. if (mFrames->HasRedecodeError()) { returnfalse;
}
switch (status) { case AnimationFrameBuffer::InsertStatus::DISCARD_CONTINUE: case AnimationFrameBuffer::InsertStatus::DISCARD_YIELD:
RequestFrameDiscarding(); break; case AnimationFrameBuffer::InsertStatus::CONTINUE: case AnimationFrameBuffer::InsertStatus::YIELD: break; default:
MOZ_ASSERT_UNREACHABLE("Unhandled insert status!"); break;
}
// We only want to handle the first frame if it is the first pass for the // animation decoder. The owning image will be cleared after that. if (mFrames->Size() == 1 && mImage) {
justGotFirstFrame = true;
}
}
if (justGotFirstFrame) {
AnnounceSurfaceAvailable();
}
// We just got the first frame; let the surface cache know. We deliberately do // this outside of mFramesMutex to avoid a potential deadlock with // AddSizeOfExcludingThis(), since otherwise we'd be acquiring mFramesMutex // and then the surface cache lock, while the memory reporting code would // acquire the surface cache lock and then mFramesMutex.
SurfaceCache::SurfaceAvailable(WrapNotNull(this));
}
if (mImage) { // Send notifications.
NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
}
// Determine if we need to recreate the decoder, in case we are discarding // frames and need to loop back to the beginning. bool recreateDecoder;
{
MutexAutoLock lock(mFramesMutex);
recreateDecoder = !mFrames->HasRedecodeError() && mFrames->MayDiscard();
}
// We don't need a reference to our image anymore, either, and we don't want // one. We may be stored in the surface cache for a long time after decoding // finishes. If we don't drop our reference to the image, we'll end up // keeping it alive as long as we remain in the surface cache, which could // greatly extend the image's lifetime - in fact, if the image isn't // discardable, it'd result in a leak!
DropImageReference();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.