Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit 1f92f3a

Browse files
committed
Bug 1293472 (Part 3) - Store animated images in the surface cache as a sequence of frames, rather than each frame getting its own cache entry. r=dholbert,edwin,njn
1 parent 367e148 commit 1f92f3a

21 files changed

Lines changed: 308 additions & 301 deletions

image/AnimationSurfaceProvider.cpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,21 @@ AnimationSurfaceProvider::LogicalSizeInBytes() const
115115
return 3 * size.width * size.height * sizeof(uint32_t);
116116
}
117117

118+
void
119+
AnimationSurfaceProvider::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
120+
size_t& aHeapSizeOut,
121+
size_t& aNonHeapSizeOut)
122+
{
123+
// Note that the surface cache lock is already held here, and then we acquire
124+
// mFramesMutex. For this method, this ordering is unavoidable, which means
125+
// that we must be careful to always use the same ordering elsewhere.
126+
MutexAutoLock lock(mFramesMutex);
127+
128+
for (const RawAccessFrameRef& frame : mFrames) {
129+
frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut);
130+
}
131+
}
132+
118133
void
119134
AnimationSurfaceProvider::Run()
120135
{
@@ -233,7 +248,11 @@ AnimationSurfaceProvider::AnnounceSurfaceAvailable()
233248
mFramesMutex.AssertNotCurrentThreadOwns();
234249
MOZ_ASSERT(mImage);
235250

236-
// We just got the first frame; let the surface cache know.
251+
// We just got the first frame; let the surface cache know. We deliberately do
252+
// this outside of mFramesMutex to avoid a potential deadlock with
253+
// AddSizeOfExcludingThis(), since otherwise we'd be acquiring mFramesMutex
254+
// and then the surface cache lock, while the memory reporting code would
255+
// acquire the surface cache lock and then mFramesMutex.
237256
SurfaceCache::SurfaceAvailable(WrapNotNull(this),
238257
ImageKey(mImage.get()),
239258
mSurfaceKey);

image/AnimationSurfaceProvider.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ class AnimationSurfaceProvider final
4545

4646
bool IsFinished() const override;
4747
size_t LogicalSizeInBytes() const override;
48+
void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
49+
size_t& aHeapSizeOut,
50+
size_t& aNonHeapSizeOut) override;
4851

4952
protected:
5053
DrawableFrameRef DrawableRef(size_t aFrame) override;

image/DecodedSurfaceProvider.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ DecodedSurfaceProvider::DecodedSurfaceProvider(NotNull<RasterImage*> aImage,
2727
MOZ_ASSERT(!mDecoder->IsMetadataDecode(),
2828
"Use MetadataDecodingTask for metadata decodes");
2929
MOZ_ASSERT(mDecoder->IsFirstFrameDecode(),
30-
"Use AnimationDecodingTask for animation decodes");
30+
"Use AnimationSurfaceProvider for animation decodes");
3131
}
3232

3333
DecodedSurfaceProvider::~DecodedSurfaceProvider()

image/Decoder.cpp

Lines changed: 3 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,9 @@ Decoder::Init()
9898
// Metadata decoders must not set an output size.
9999
MOZ_ASSERT_IF(mMetadataDecode, !mHaveExplicitOutputSize);
100100

101-
// It doesn't make sense to decode anything but the first frame if we can't
102-
// store anything in the SurfaceCache, since only the last frame we decode
103-
// will be retrievable.
104-
MOZ_ASSERT(ShouldUseSurfaceCache() || IsFirstFrameDecode());
101+
// All decoders must be anonymous except for metadata decoders.
102+
// XXX(seth): Soon that exception will be removed.
103+
MOZ_ASSERT_IF(mImage, IsMetadataDecode());
105104

106105
// Implementation-specific initialization.
107106
nsresult rv = InitInternal();
@@ -341,13 +340,6 @@ Decoder::AllocateFrameInternal(uint32_t aFrameNum,
341340
return RawAccessFrameRef();
342341
}
343342

344-
const uint32_t bytesPerPixel = aPaletteDepth == 0 ? 4 : 1;
345-
if (ShouldUseSurfaceCache() &&
346-
!SurfaceCache::CanHold(aFrameRect.Size(), bytesPerPixel)) {
347-
NS_WARNING("Trying to add frame that's too large for the SurfaceCache");
348-
return RawAccessFrameRef();
349-
}
350-
351343
NotNull<RefPtr<imgFrame>> frame = WrapNotNull(new imgFrame());
352344
bool nonPremult = bool(mSurfaceFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
353345
if (NS_FAILED(frame->InitForDecoder(aOutputSize, aFrameRect, aFormat,
@@ -362,29 +354,6 @@ Decoder::AllocateFrameInternal(uint32_t aFrameNum,
362354
return RawAccessFrameRef();
363355
}
364356

365-
if (ShouldUseSurfaceCache()) {
366-
NotNull<RefPtr<ISurfaceProvider>> provider =
367-
WrapNotNull(new SimpleSurfaceProvider(frame));
368-
InsertOutcome outcome =
369-
SurfaceCache::Insert(provider, ImageKey(mImage.get()),
370-
RasterSurfaceKey(aOutputSize,
371-
mSurfaceFlags,
372-
aFrameNum));
373-
if (outcome == InsertOutcome::FAILURE) {
374-
// We couldn't insert the surface, almost certainly due to low memory. We
375-
// treat this as a permanent error to help the system recover; otherwise,
376-
// we might just end up attempting to decode this image again immediately.
377-
ref->Abort();
378-
return RawAccessFrameRef();
379-
} else if (outcome == InsertOutcome::FAILURE_ALREADY_PRESENT) {
380-
// Another decoder beat us to decoding this frame. We abort this decoder
381-
// rather than treat this as a real error.
382-
mDecodeAborted = true;
383-
ref->Abort();
384-
return RawAccessFrameRef();
385-
}
386-
}
387-
388357
if (aFrameNum == 1) {
389358
MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
390359
aPreviousFrame->SetRawAccessOnly();

image/Decoder.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,9 +287,6 @@ class Decoder
287287
/// Are we in the middle of a frame right now? Used for assertions only.
288288
bool InFrame() const { return mInFrame; }
289289

290-
/// Should we store surfaces created by this decoder in the SurfaceCache?
291-
bool ShouldUseSurfaceCache() const { return bool(mImage); }
292-
293290
/**
294291
* Returns true if this decoder was aborted.
295292
*

image/DecoderFactory.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "nsMimeTypes.h"
99
#include "mozilla/RefPtr.h"
1010

11+
#include "AnimationSurfaceProvider.h"
1112
#include "Decoder.h"
1213
#include "IDecodingTask.h"
1314
#include "nsPNGDecoder.h"
@@ -141,7 +142,7 @@ DecoderFactory::CreateDecoder(DecoderType aType,
141142
// Create a DecodedSurfaceProvider which will manage the decoding process and
142143
// make this decoder's output available in the surface cache.
143144
SurfaceKey surfaceKey =
144-
RasterSurfaceKey(aOutputSize, aSurfaceFlags, /* aFrameNum = */ 0);
145+
RasterSurfaceKey(aOutputSize, aSurfaceFlags, PlaybackType::eStatic);
145146
NotNull<RefPtr<DecodedSurfaceProvider>> provider =
146147
WrapNotNull(new DecodedSurfaceProvider(aImage,
147148
WrapNotNull(decoder),
@@ -175,8 +176,9 @@ DecoderFactory::CreateAnimationDecoder(DecoderType aType,
175176
MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG,
176177
"Calling CreateAnimationDecoder for non-animating DecoderType");
177178

178-
RefPtr<Decoder> decoder =
179-
GetDecoder(aType, aImage, /* aIsRedecode = */ true);
179+
// Create an anonymous decoder. Interaction with the SurfaceCache and the
180+
// owning RasterImage will be mediated by AnimationSurfaceProvider.
181+
RefPtr<Decoder> decoder = GetDecoder(aType, nullptr, /* aIsRedecode = */ true);
180182
MOZ_ASSERT(decoder, "Should have a decoder now");
181183

182184
// Initialize the decoder.
@@ -189,17 +191,25 @@ DecoderFactory::CreateAnimationDecoder(DecoderType aType,
189191
return nullptr;
190192
}
191193

192-
// Add a placeholder for the first frame to the SurfaceCache so we won't
193-
// trigger any more decoders with the same parameters.
194+
// Create an AnimationSurfaceProvider which will manage the decoding process
195+
// and make this decoder's output available in the surface cache.
194196
SurfaceKey surfaceKey =
195-
RasterSurfaceKey(aIntrinsicSize, aSurfaceFlags, /* aFrameNum = */ 0);
197+
RasterSurfaceKey(aIntrinsicSize, aSurfaceFlags, PlaybackType::eAnimated);
198+
NotNull<RefPtr<AnimationSurfaceProvider>> provider =
199+
WrapNotNull(new AnimationSurfaceProvider(aImage,
200+
WrapNotNull(decoder),
201+
surfaceKey));
202+
203+
// Attempt to insert the surface provider into the surface cache right away so
204+
// we won't trigger any more decoders with the same parameters.
196205
InsertOutcome outcome =
197-
SurfaceCache::InsertPlaceholder(ImageKey(aImage.get()), surfaceKey);
206+
SurfaceCache::Insert(provider, ImageKey(aImage.get()), surfaceKey);
198207
if (outcome != InsertOutcome::SUCCESS) {
199208
return nullptr;
200209
}
201210

202-
RefPtr<IDecodingTask> task = new AnimationDecodingTask(WrapNotNull(decoder));
211+
// Return the surface provider in its IDecodingTask guise.
212+
RefPtr<IDecodingTask> task = provider.get();
203213
return task.forget();
204214
}
205215

image/FrameAnimator.cpp

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,6 @@ FrameAnimator::RequestRefresh(AnimationState& aState, const TimeStamp& aTime)
297297
LookupResult
298298
FrameAnimator::GetCompositedFrame(uint32_t aFrameNum)
299299
{
300-
MOZ_ASSERT(aFrameNum != 0, "First frame is never composited");
301-
302300
// If we have a composited version of this frame, return that.
303301
if (mLastCompositedFrameIndex == int32_t(aFrameNum)) {
304302
return LookupResult(DrawableSurface(mCompositingFrame->DrawableRef()),
@@ -311,9 +309,20 @@ FrameAnimator::GetCompositedFrame(uint32_t aFrameNum)
311309
SurfaceCache::Lookup(ImageKey(mImage),
312310
RasterSurfaceKey(mSize,
313311
DefaultSurfaceFlags(),
314-
aFrameNum));
315-
MOZ_ASSERT(!result || !result.Surface()->GetIsPaletted(),
312+
PlaybackType::eAnimated));
313+
if (!result) {
314+
return result;
315+
}
316+
317+
// Seek to the appropriate frame. If seeking fails, it means that we couldn't
318+
// get the frame we're looking for; treat this as if the lookup failed.
319+
if (NS_FAILED(result.Surface().Seek(aFrameNum))) {
320+
return LookupResult(MatchType::NOT_FOUND);
321+
}
322+
323+
MOZ_ASSERT(!result.Surface()->GetIsPaletted(),
316324
"About to return a paletted frame");
325+
317326
return result;
318327
}
319328

@@ -339,7 +348,7 @@ DoCollectSizeOfCompositingSurfaces(const RawAccessFrameRef& aSurface,
339348
// Concoct a SurfaceKey for this surface.
340349
SurfaceKey key = RasterSurfaceKey(aSurface->GetImageSize(),
341350
DefaultSurfaceFlags(),
342-
/* aFrameNum = */ 0);
351+
PlaybackType::eStatic);
343352

344353
// Create a counter for this surface.
345354
SurfaceMemoryCounter counter(key, /* aIsLocked = */ true, aType);
@@ -381,9 +390,19 @@ FrameAnimator::GetRawFrame(uint32_t aFrameNum) const
381390
SurfaceCache::Lookup(ImageKey(mImage),
382391
RasterSurfaceKey(mSize,
383392
DefaultSurfaceFlags(),
384-
aFrameNum));
385-
return result ? result.Surface()->RawAccessRef()
386-
: RawAccessFrameRef();
393+
PlaybackType::eAnimated));
394+
if (!result) {
395+
return RawAccessFrameRef();
396+
}
397+
398+
// Seek to the frame we want. If seeking fails, it means we couldn't get the
399+
// frame we're looking for, so we bail here to avoid returning the wrong frame
400+
// to the caller.
401+
if (NS_FAILED(result.Surface().Seek(aFrameNum))) {
402+
return RawAccessFrameRef(); // Not available yet.
403+
}
404+
405+
return result.Surface()->RawAccessRef();
387406
}
388407

389408
//******************************************************************************

image/IDecodingTask.cpp

Lines changed: 12 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,21 @@ IDecodingTask::NotifyProgress(NotNull<RasterImage*> aImage,
3838
Progress progress = aDecoder->TakeProgress();
3939
IntRect invalidRect = aDecoder->TakeInvalidRect();
4040
Maybe<uint32_t> frameCount = aDecoder->TakeCompleteFrameCount();
41+
DecoderFlags decoderFlags = aDecoder->GetDecoderFlags();
4142
SurfaceFlags surfaceFlags = aDecoder->GetSurfaceFlags();
4243

4344
// Synchronously notify if we can.
44-
if (NS_IsMainThread() &&
45-
!(aDecoder->GetDecoderFlags() & DecoderFlags::ASYNC_NOTIFY)) {
46-
aImage->NotifyProgress(progress, invalidRect,
47-
frameCount, surfaceFlags);
45+
if (NS_IsMainThread() && !(decoderFlags & DecoderFlags::ASYNC_NOTIFY)) {
46+
aImage->NotifyProgress(progress, invalidRect, frameCount,
47+
decoderFlags, surfaceFlags);
4848
return;
4949
}
5050

5151
// We're forced to notify asynchronously.
5252
NotNull<RefPtr<RasterImage>> image = aImage;
5353
NS_DispatchToMainThread(NS_NewRunnableFunction([=]() -> void {
54-
image->NotifyProgress(progress, invalidRect,
55-
frameCount, surfaceFlags);
54+
image->NotifyProgress(progress, invalidRect, frameCount,
55+
decoderFlags, surfaceFlags);
5656
}));
5757
}
5858

@@ -70,21 +70,23 @@ IDecodingTask::NotifyDecodeComplete(NotNull<RasterImage*> aImage,
7070
Progress progress = aDecoder->TakeProgress();
7171
IntRect invalidRect = aDecoder->TakeInvalidRect();
7272
Maybe<uint32_t> frameCount = aDecoder->TakeCompleteFrameCount();
73+
DecoderFlags decoderFlags = aDecoder->GetDecoderFlags();
7374
SurfaceFlags surfaceFlags = aDecoder->GetSurfaceFlags();
7475

7576
// Synchronously notify if we can.
76-
if (NS_IsMainThread() &&
77-
!(aDecoder->GetDecoderFlags() & DecoderFlags::ASYNC_NOTIFY)) {
77+
if (NS_IsMainThread() && !(decoderFlags & DecoderFlags::ASYNC_NOTIFY)) {
7878
aImage->NotifyDecodeComplete(finalStatus, metadata, telemetry, progress,
79-
invalidRect, frameCount, surfaceFlags);
79+
invalidRect, frameCount, decoderFlags,
80+
surfaceFlags);
8081
return;
8182
}
8283

8384
// We're forced to notify asynchronously.
8485
NotNull<RefPtr<RasterImage>> image = aImage;
8586
NS_DispatchToMainThread(NS_NewRunnableFunction([=]() -> void {
8687
image->NotifyDecodeComplete(finalStatus, metadata, telemetry, progress,
87-
invalidRect, frameCount, surfaceFlags);
88+
invalidRect, frameCount, decoderFlags,
89+
surfaceFlags);
8890
}));
8991
}
9092

@@ -100,59 +102,6 @@ IDecodingTask::Resume()
100102
}
101103

102104

103-
///////////////////////////////////////////////////////////////////////////////
104-
// AnimationDecodingTask implementation.
105-
///////////////////////////////////////////////////////////////////////////////
106-
107-
AnimationDecodingTask::AnimationDecodingTask(NotNull<Decoder*> aDecoder)
108-
: mMutex("mozilla::image::AnimationDecodingTask")
109-
, mDecoder(aDecoder)
110-
{
111-
MOZ_ASSERT(!mDecoder->IsMetadataDecode(),
112-
"Use MetadataDecodingTask for metadata decodes");
113-
MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(),
114-
"Use DecodingTask for single-frame image decodes");
115-
}
116-
117-
void
118-
AnimationDecodingTask::Run()
119-
{
120-
MutexAutoLock lock(mMutex);
121-
122-
while (true) {
123-
LexerResult result = mDecoder->Decode(WrapNotNull(this));
124-
125-
if (result.is<TerminalState>()) {
126-
NotifyDecodeComplete(mDecoder->GetImage(), mDecoder);
127-
return; // We're done.
128-
}
129-
130-
MOZ_ASSERT(result.is<Yield>());
131-
132-
// Notify for the progress we've made so far.
133-
if (mDecoder->HasProgress()) {
134-
NotifyProgress(mDecoder->GetImage(), mDecoder);
135-
}
136-
137-
if (result == LexerResult(Yield::NEED_MORE_DATA)) {
138-
// We can't make any more progress right now. The decoder itself will
139-
// ensure that we get reenqueued when more data is available; just return
140-
// for now.
141-
return;
142-
}
143-
144-
// Right now we don't do anything special for other kinds of yields, so just
145-
// keep working.
146-
}
147-
}
148-
149-
bool
150-
AnimationDecodingTask::ShouldPreferSyncRun() const
151-
{
152-
return mDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime());
153-
}
154-
155-
156105
///////////////////////////////////////////////////////////////////////////////
157106
// MetadataDecodingTask implementation.
158107
///////////////////////////////////////////////////////////////////////////////

image/IDecodingTask.h

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -62,33 +62,6 @@ class IDecodingTask : public IResumable
6262
};
6363

6464

65-
/**
66-
* An IDecodingTask implementation for full decodes of animated images.
67-
*/
68-
class AnimationDecodingTask final : public IDecodingTask
69-
{
70-
public:
71-
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnimationDecodingTask, override)
72-
73-
explicit AnimationDecodingTask(NotNull<Decoder*> aDecoder);
74-
75-
void Run() override;
76-
bool ShouldPreferSyncRun() const override;
77-
78-
// Full decodes are low priority compared to metadata decodes because they
79-
// don't block layout or page load.
80-
TaskPriority Priority() const override { return TaskPriority::eLow; }
81-
82-
private:
83-
virtual ~AnimationDecodingTask() { }
84-
85-
/// Mutex protecting access to mDecoder.
86-
Mutex mMutex;
87-
88-
NotNull<RefPtr<Decoder>> mDecoder;
89-
};
90-
91-
9265
/**
9366
* An IDecodingTask implementation for metadata decodes of images.
9467
*/

0 commit comments

Comments
 (0)