mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-04 23:08:39 +09:00
Issue #2073 - m-c 523950: Discard decoded frames of very large GIF animations (squashed)
Controlled by image.animated.decode-on-demand.threshold-kb, default 256MB Includes squashed bugfixes/regressions: - m-c 1444537: Shutting down the decode pool should make animated decoders bail early - m-c 1628606: Make sure to mark the surface cache entry available before sending the frame complete notification - m-c 1502275: Skip recreating the decoder after redecode errors if an animated image is reset - m-c 1443232: Don't insert frames into our AnimationFrameBuffer that we consider in error and unusable
This commit is contained in:
parent
eac8afce35
commit
e96122ede2
25 changed files with 925 additions and 63 deletions
|
|
@ -8,6 +8,7 @@
|
|||
#include "gfxPrefs.h"
|
||||
#include "nsProxyRelease.h"
|
||||
|
||||
#include "DecodePool.h"
|
||||
#include "Decoder.h"
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
|
|
@ -17,7 +18,8 @@ namespace image {
|
|||
|
||||
AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
|
||||
const SurfaceKey& aSurfaceKey,
|
||||
NotNull<Decoder*> aDecoder)
|
||||
NotNull<Decoder*> aDecoder,
|
||||
size_t aCurrentFrame)
|
||||
: ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey,
|
||||
AvailabilityState::StartAsPlaceholder())
|
||||
, mImage(aImage.get())
|
||||
|
|
@ -29,6 +31,22 @@ AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
|
|||
"Use MetadataDecodingTask for metadata decodes");
|
||||
MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(),
|
||||
"Use DecodedSurfaceProvider for single-frame image decodes");
|
||||
|
||||
// We still produce paletted surfaces for GIF which means the frames are
|
||||
// smaller than one would expect for APNG. This may be removed if/when
|
||||
// bug 1337111 lands and it is enabled by default.
|
||||
size_t pixelSize = aDecoder->GetType() == DecoderType::GIF
|
||||
? sizeof(uint8_t) : sizeof(uint32_t);
|
||||
|
||||
// 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(gfxPrefs::ImageAnimatedDecodeOnDemandThresholdKB()) * 1024) /
|
||||
(pixelSize * frameSize.width * frameSize.height);
|
||||
size_t batch = gfxPrefs::ImageAnimatedDecodeOnDemandBatchSize();
|
||||
|
||||
mFrames.Initialize(threshold, batch, aCurrentFrame);
|
||||
}
|
||||
|
||||
AnimationSurfaceProvider::~AnimationSurfaceProvider()
|
||||
|
|
@ -53,6 +71,75 @@ AnimationSurfaceProvider::DropImageReference()
|
|||
NS_ReleaseOnMainThread(image.forget(), /* aAlwaysProxy = */ true);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
MutexAutoLock lock2(mFramesMutex);
|
||||
restartDecoder = mFrames.Reset();
|
||||
} else {
|
||||
MOZ_ASSERT(mFrames.HasRedecodeError());
|
||||
}
|
||||
}
|
||||
|
||||
if (restartDecoder) {
|
||||
DecodePool::Singleton()->AsyncRun(this);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AnimationSurfaceProvider::Advance(size_t aFrame)
|
||||
{
|
||||
bool restartDecoder;
|
||||
|
||||
{
|
||||
// Typical advancement of a frame.
|
||||
MutexAutoLock lock(mFramesMutex);
|
||||
restartDecoder = mFrames.AdvanceTo(aFrame);
|
||||
}
|
||||
|
||||
if (restartDecoder) {
|
||||
DecodePool::Singleton()->AsyncRun(this);
|
||||
}
|
||||
}
|
||||
|
||||
DrawableFrameRef
|
||||
AnimationSurfaceProvider::DrawableRef(size_t aFrame)
|
||||
{
|
||||
|
|
@ -63,19 +150,7 @@ AnimationSurfaceProvider::DrawableRef(size_t aFrame)
|
|||
return DrawableFrameRef();
|
||||
}
|
||||
|
||||
if (mFrames.IsEmpty()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() when we have no frames");
|
||||
return DrawableFrameRef();
|
||||
}
|
||||
|
||||
// If we don't have that frame, return an empty frame ref.
|
||||
if (aFrame >= mFrames.Length()) {
|
||||
return DrawableFrameRef();
|
||||
}
|
||||
|
||||
// We've got the requested frame. Return it.
|
||||
MOZ_ASSERT(mFrames[aFrame]);
|
||||
return mFrames[aFrame]->DrawableRef();
|
||||
return mFrames.Get(aFrame);
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
@ -88,13 +163,20 @@ AnimationSurfaceProvider::IsFinished() const
|
|||
return false;
|
||||
}
|
||||
|
||||
if (mFrames.IsEmpty()) {
|
||||
if (mFrames.Frames().IsEmpty()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no frames");
|
||||
return false;
|
||||
}
|
||||
|
||||
// As long as we have at least one finished frame, we're finished.
|
||||
return mFrames[0]->IsFinished();
|
||||
return mFrames.Frames()[0]->IsFinished();
|
||||
}
|
||||
|
||||
bool
|
||||
AnimationSurfaceProvider::IsFullyDecoded() const
|
||||
{
|
||||
MutexAutoLock lock(mFramesMutex);
|
||||
return mFrames.SizeKnown() && !mFrames.MayDiscard();
|
||||
}
|
||||
|
||||
size_t
|
||||
|
|
@ -125,8 +207,10 @@ AnimationSurfaceProvider::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
|
|||
// that we must be careful to always use the same ordering elsewhere.
|
||||
MutexAutoLock lock(mFramesMutex);
|
||||
|
||||
for (const RawAccessFrameRef& frame : mFrames) {
|
||||
frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut);
|
||||
for (const RawAccessFrameRef& frame : mFrames.Frames()) {
|
||||
if (frame) {
|
||||
frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +219,7 @@ AnimationSurfaceProvider::Run()
|
|||
{
|
||||
MutexAutoLock lock(mDecodingMutex);
|
||||
|
||||
if (!mDecoder || !mImage) {
|
||||
if (!mDecoder) {
|
||||
MOZ_ASSERT_UNREACHABLE("Running after decoding finished?");
|
||||
return;
|
||||
}
|
||||
|
|
@ -150,15 +234,34 @@ AnimationSurfaceProvider::Run()
|
|||
// Since we're not sure, rather than call CheckForNewFrameAtYield() here
|
||||
// we call CheckForNewFrameAtTerminalState(), which handles both of these
|
||||
// possibilities.
|
||||
CheckForNewFrameAtTerminalState();
|
||||
|
||||
// We're done!
|
||||
bool continueDecoding = CheckForNewFrameAtTerminalState();
|
||||
FinishDecoding();
|
||||
return;
|
||||
|
||||
// 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::Singleton()->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 (mDecoder->HasProgress()) {
|
||||
if (mImage && mDecoder->HasProgress()) {
|
||||
NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder));
|
||||
}
|
||||
|
||||
|
|
@ -168,38 +271,52 @@ AnimationSurfaceProvider::Run()
|
|||
return;
|
||||
}
|
||||
|
||||
// There's new output available - a new frame! Grab it.
|
||||
// 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));
|
||||
CheckForNewFrameAtYield();
|
||||
if (!checkForNewFrameAtYieldResult ||
|
||||
DecodePool::Singleton()->IsShuttingDown()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
AnimationSurfaceProvider::CheckForNewFrameAtYield()
|
||||
{
|
||||
mDecodingMutex.AssertCurrentThreadOwns();
|
||||
MOZ_ASSERT(mDecoder);
|
||||
|
||||
bool justGotFirstFrame = false;
|
||||
bool continueDecoding;
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mFramesMutex);
|
||||
|
||||
// Try to get the new frame from the decoder.
|
||||
RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
|
||||
MOZ_ASSERT(mDecoder->HasFrameToTake());
|
||||
mDecoder->ClearHasFrameToTake();
|
||||
|
||||
if (!frame) {
|
||||
MOZ_ASSERT_UNREACHABLE("Decoder yielded but didn't produce a frame?");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We should've gotten a different frame than last time.
|
||||
MOZ_ASSERT_IF(!mFrames.IsEmpty(),
|
||||
mFrames.LastElement().get() != frame.get());
|
||||
MOZ_ASSERT_IF(!mFrames.Frames().IsEmpty(),
|
||||
mFrames.Frames().LastElement().get() != frame.get());
|
||||
|
||||
// Append the new frame to the list.
|
||||
mFrames.AppendElement(Move(frame));
|
||||
continueDecoding = mFrames.Insert(Move(frame));
|
||||
|
||||
if (mFrames.Length() == 1) {
|
||||
// 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.Frames().Length();
|
||||
if (frameCount == 1 && mImage) {
|
||||
justGotFirstFrame = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -207,32 +324,49 @@ AnimationSurfaceProvider::CheckForNewFrameAtYield()
|
|||
if (justGotFirstFrame) {
|
||||
AnnounceSurfaceAvailable();
|
||||
}
|
||||
|
||||
return continueDecoding;
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
AnimationSurfaceProvider::CheckForNewFrameAtTerminalState()
|
||||
{
|
||||
mDecodingMutex.AssertCurrentThreadOwns();
|
||||
MOZ_ASSERT(mDecoder);
|
||||
|
||||
bool justGotFirstFrame = false;
|
||||
bool continueDecoding;
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mFramesMutex);
|
||||
|
||||
// The decoder may or may not have a new frame for us at this point. Avoid
|
||||
// reinserting the same frame again.
|
||||
RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
|
||||
if (!frame) {
|
||||
return;
|
||||
|
||||
// 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 = RawAccessFrameRef();
|
||||
} else {
|
||||
MOZ_ASSERT(frame);
|
||||
mDecoder->ClearHasFrameToTake();
|
||||
}
|
||||
|
||||
if (!mFrames.IsEmpty() && mFrames.LastElement().get() == frame.get()) {
|
||||
return; // We already have this one.
|
||||
if (!frame || (!mFrames.Frames().IsEmpty() &&
|
||||
mFrames.Frames().LastElement().get() == frame.get())) {
|
||||
return mFrames.MarkComplete();
|
||||
}
|
||||
|
||||
// Append the new frame to the list.
|
||||
mFrames.AppendElement(Move(frame));
|
||||
mFrames.Insert(Move(frame));
|
||||
continueDecoding = mFrames.MarkComplete();
|
||||
|
||||
if (mFrames.Length() == 1) {
|
||||
// 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.Frames().Length() == 1 && mImage) {
|
||||
justGotFirstFrame = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -240,6 +374,8 @@ AnimationSurfaceProvider::CheckForNewFrameAtTerminalState()
|
|||
if (justGotFirstFrame) {
|
||||
AnnounceSurfaceAvailable();
|
||||
}
|
||||
|
||||
return continueDecoding;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -260,14 +396,27 @@ void
|
|||
AnimationSurfaceProvider::FinishDecoding()
|
||||
{
|
||||
mDecodingMutex.AssertCurrentThreadOwns();
|
||||
MOZ_ASSERT(mImage);
|
||||
MOZ_ASSERT(mDecoder);
|
||||
|
||||
// Send notifications.
|
||||
NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
|
||||
if (mImage) {
|
||||
// Send notifications.
|
||||
NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
|
||||
}
|
||||
|
||||
// Destroy our decoder; we don't need it anymore.
|
||||
mDecoder = nullptr;
|
||||
// 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();
|
||||
}
|
||||
|
||||
if (recreateDecoder) {
|
||||
mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder);
|
||||
MOZ_ASSERT(mDecoder);
|
||||
} else {
|
||||
mDecoder = nullptr;
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue