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

Commit 367e148

Browse files
committed
Bug 1293472 (Part 2) - Add AnimationSurfaceProvider. r=dholbert,edwin
1 parent aef6675 commit 367e148

3 files changed

Lines changed: 379 additions & 0 deletions

File tree

image/AnimationSurfaceProvider.cpp

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
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 "AnimationSurfaceProvider.h"
7+
8+
#include "gfxPrefs.h"
9+
#include "nsProxyRelease.h"
10+
11+
#include "Decoder.h"
12+
13+
using namespace mozilla::gfx;
14+
15+
namespace mozilla {
16+
namespace image {
17+
18+
AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
19+
NotNull<Decoder*> aDecoder,
20+
const SurfaceKey& aSurfaceKey)
21+
: ISurfaceProvider(AvailabilityState::StartAsPlaceholder())
22+
, mImage(aImage.get())
23+
, mDecodingMutex("AnimationSurfaceProvider::mDecoder")
24+
, mDecoder(aDecoder.get())
25+
, mFramesMutex("AnimationSurfaceProvider::mFrames")
26+
, mSurfaceKey(aSurfaceKey)
27+
{
28+
MOZ_ASSERT(!mDecoder->IsMetadataDecode(),
29+
"Use MetadataDecodingTask for metadata decodes");
30+
MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(),
31+
"Use DecodedSurfaceProvider for single-frame image decodes");
32+
}
33+
34+
AnimationSurfaceProvider::~AnimationSurfaceProvider()
35+
{
36+
DropImageReference();
37+
}
38+
39+
void
40+
AnimationSurfaceProvider::DropImageReference()
41+
{
42+
if (!mImage) {
43+
return; // Nothing to do.
44+
}
45+
46+
// RasterImage objects need to be destroyed on the main thread. We also need
47+
// to destroy them asynchronously, because if our surface cache entry is
48+
// destroyed and we were the only thing keeping |mImage| alive, RasterImage's
49+
// destructor may call into the surface cache while whatever code caused us to
50+
// get evicted is holding the surface cache lock, causing deadlock.
51+
RefPtr<RasterImage> image = mImage;
52+
mImage = nullptr;
53+
NS_ReleaseOnMainThread(image.forget(), /* aAlwaysProxy = */ true);
54+
}
55+
56+
DrawableFrameRef
57+
AnimationSurfaceProvider::DrawableRef(size_t aFrame)
58+
{
59+
MutexAutoLock lock(mFramesMutex);
60+
61+
if (Availability().IsPlaceholder()) {
62+
MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() on a placeholder");
63+
return DrawableFrameRef();
64+
}
65+
66+
if (mFrames.IsEmpty()) {
67+
MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() when we have no frames");
68+
return DrawableFrameRef();
69+
}
70+
71+
// If we don't have that frame, return an empty frame ref.
72+
if (aFrame >= mFrames.Length()) {
73+
return DrawableFrameRef();
74+
}
75+
76+
// We've got the requested frame. Return it.
77+
MOZ_ASSERT(mFrames[aFrame]);
78+
return mFrames[aFrame]->DrawableRef();
79+
}
80+
81+
bool
82+
AnimationSurfaceProvider::IsFinished() const
83+
{
84+
MutexAutoLock lock(mFramesMutex);
85+
86+
if (Availability().IsPlaceholder()) {
87+
MOZ_ASSERT_UNREACHABLE("Calling IsFinished() on a placeholder");
88+
return false;
89+
}
90+
91+
if (mFrames.IsEmpty()) {
92+
MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no frames");
93+
return false;
94+
}
95+
96+
// As long as we have at least one finished frame, we're finished.
97+
return mFrames[0]->IsFinished();
98+
}
99+
100+
size_t
101+
AnimationSurfaceProvider::LogicalSizeInBytes() const
102+
{
103+
// When decoding animated images, we need at most three live surfaces: the
104+
// composited surface, the previous composited surface for
105+
// DisposalMethod::RESTORE_PREVIOUS, and the surface we're currently decoding
106+
// into. The composited surfaces are always BGRA. Although the surface we're
107+
// decoding into may be paletted, and may be smaller than the real size of the
108+
// image, we assume the worst case here.
109+
// XXX(seth): Note that this is actually not accurate yet; we're storing the
110+
// full sequence of frames, not just the three live surfaces mentioned above.
111+
// Unfortunately there's no way to know in advance how many frames an
112+
// animation has, so we really can't do better here. This will become correct
113+
// once bug 1289954 is complete.
114+
IntSize size = mSurfaceKey.Size();
115+
return 3 * size.width * size.height * sizeof(uint32_t);
116+
}
117+
118+
void
119+
AnimationSurfaceProvider::Run()
120+
{
121+
MutexAutoLock lock(mDecodingMutex);
122+
123+
if (!mDecoder || !mImage) {
124+
MOZ_ASSERT_UNREACHABLE("Running after decoding finished?");
125+
return;
126+
}
127+
128+
while (true) {
129+
// Run the decoder.
130+
LexerResult result = mDecoder->Decode(WrapNotNull(this));
131+
132+
if (result.is<TerminalState>()) {
133+
// We may have a new frame now, but it's not guaranteed - a decoding
134+
// failure or truncated data may mean that no new frame got produced.
135+
// Since we're not sure, rather than call CheckForNewFrameAtYield() here
136+
// we call CheckForNewFrameAtTerminalState(), which handles both of these
137+
// possibilities.
138+
CheckForNewFrameAtTerminalState();
139+
140+
// We're done!
141+
FinishDecoding();
142+
return;
143+
}
144+
145+
// Notify for the progress we've made so far.
146+
if (mDecoder->HasProgress()) {
147+
NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder));
148+
}
149+
150+
if (result == LexerResult(Yield::NEED_MORE_DATA)) {
151+
// We can't make any more progress right now. The decoder itself will ensure
152+
// that we get reenqueued when more data is available; just return for now.
153+
return;
154+
}
155+
156+
// There's new output available - a new frame! Grab it.
157+
MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE));
158+
CheckForNewFrameAtYield();
159+
}
160+
}
161+
162+
void
163+
AnimationSurfaceProvider::CheckForNewFrameAtYield()
164+
{
165+
mDecodingMutex.AssertCurrentThreadOwns();
166+
MOZ_ASSERT(mDecoder);
167+
168+
bool justGotFirstFrame = false;
169+
170+
{
171+
MutexAutoLock lock(mFramesMutex);
172+
173+
// Try to get the new frame from the decoder.
174+
RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
175+
if (!frame) {
176+
MOZ_ASSERT_UNREACHABLE("Decoder yielded but didn't produce a frame?");
177+
return;
178+
}
179+
180+
// We should've gotten a different frame than last time.
181+
MOZ_ASSERT_IF(!mFrames.IsEmpty(),
182+
mFrames.LastElement().get() != frame.get());
183+
184+
// Append the new frame to the list.
185+
mFrames.AppendElement(Move(frame));
186+
187+
if (mFrames.Length() == 1) {
188+
justGotFirstFrame = true;
189+
}
190+
}
191+
192+
if (justGotFirstFrame) {
193+
AnnounceSurfaceAvailable();
194+
}
195+
}
196+
197+
void
198+
AnimationSurfaceProvider::CheckForNewFrameAtTerminalState()
199+
{
200+
mDecodingMutex.AssertCurrentThreadOwns();
201+
MOZ_ASSERT(mDecoder);
202+
203+
bool justGotFirstFrame = false;
204+
205+
{
206+
MutexAutoLock lock(mFramesMutex);
207+
208+
RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
209+
if (!frame) {
210+
return;
211+
}
212+
213+
if (!mFrames.IsEmpty() && mFrames.LastElement().get() == frame.get()) {
214+
return; // We already have this one.
215+
}
216+
217+
// Append the new frame to the list.
218+
mFrames.AppendElement(Move(frame));
219+
220+
if (mFrames.Length() == 1) {
221+
justGotFirstFrame = true;
222+
}
223+
}
224+
225+
if (justGotFirstFrame) {
226+
AnnounceSurfaceAvailable();
227+
}
228+
}
229+
230+
void
231+
AnimationSurfaceProvider::AnnounceSurfaceAvailable()
232+
{
233+
mFramesMutex.AssertNotCurrentThreadOwns();
234+
MOZ_ASSERT(mImage);
235+
236+
// We just got the first frame; let the surface cache know.
237+
SurfaceCache::SurfaceAvailable(WrapNotNull(this),
238+
ImageKey(mImage.get()),
239+
mSurfaceKey);
240+
}
241+
242+
void
243+
AnimationSurfaceProvider::FinishDecoding()
244+
{
245+
mDecodingMutex.AssertCurrentThreadOwns();
246+
MOZ_ASSERT(mImage);
247+
MOZ_ASSERT(mDecoder);
248+
249+
// Send notifications.
250+
NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
251+
252+
// Destroy our decoder; we don't need it anymore.
253+
mDecoder = nullptr;
254+
255+
// We don't need a reference to our image anymore, either, and we don't want
256+
// one. We may be stored in the surface cache for a long time after decoding
257+
// finishes. If we don't drop our reference to the image, we'll end up
258+
// keeping it alive as long as we remain in the surface cache, which could
259+
// greatly extend the image's lifetime - in fact, if the image isn't
260+
// discardable, it'd result in a leak!
261+
DropImageReference();
262+
}
263+
264+
bool
265+
AnimationSurfaceProvider::ShouldPreferSyncRun() const
266+
{
267+
MutexAutoLock lock(mDecodingMutex);
268+
MOZ_ASSERT(mDecoder);
269+
270+
return mDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime());
271+
}
272+
273+
} // namespace image
274+
} // namespace mozilla

image/AnimationSurfaceProvider.h

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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+
/**
7+
* An ISurfaceProvider for animated images.
8+
*/
9+
10+
#ifndef mozilla_image_AnimationSurfaceProvider_h
11+
#define mozilla_image_AnimationSurfaceProvider_h
12+
13+
#include "FrameAnimator.h"
14+
#include "IDecodingTask.h"
15+
#include "ISurfaceProvider.h"
16+
17+
namespace mozilla {
18+
namespace image {
19+
20+
/**
21+
* An ISurfaceProvider that manages the decoding of animated images and
22+
* dynamically generates surfaces for the current playback state of the
23+
* animation.
24+
*/
25+
class AnimationSurfaceProvider final
26+
: public ISurfaceProvider
27+
, public IDecodingTask
28+
{
29+
public:
30+
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnimationSurfaceProvider, override)
31+
32+
AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
33+
NotNull<Decoder*> aDecoder,
34+
const SurfaceKey& aSurfaceKey);
35+
36+
37+
//////////////////////////////////////////////////////////////////////////////
38+
// ISurfaceProvider implementation.
39+
//////////////////////////////////////////////////////////////////////////////
40+
41+
public:
42+
// We use the ISurfaceProvider constructor of DrawableSurface to indicate that
43+
// our surfaces are computed lazily.
44+
DrawableSurface Surface() override { return DrawableSurface(WrapNotNull(this)); }
45+
46+
bool IsFinished() const override;
47+
size_t LogicalSizeInBytes() const override;
48+
49+
protected:
50+
DrawableFrameRef DrawableRef(size_t aFrame) override;
51+
52+
// Animation frames are always locked. This is because we only want to release
53+
// their memory atomically (due to the surface cache discarding them). If they
54+
// were unlocked, the OS could end up releasing the memory of random frames
55+
// from the middle of the animation, which is not worth the complexity of
56+
// dealing with.
57+
bool IsLocked() const override { return true; }
58+
void SetLocked(bool) override { }
59+
60+
61+
//////////////////////////////////////////////////////////////////////////////
62+
// IDecodingTask implementation.
63+
//////////////////////////////////////////////////////////////////////////////
64+
65+
public:
66+
void Run() override;
67+
bool ShouldPreferSyncRun() const override;
68+
69+
// Full decodes are low priority compared to metadata decodes because they
70+
// don't block layout or page load.
71+
TaskPriority Priority() const override { return TaskPriority::eLow; }
72+
73+
private:
74+
virtual ~AnimationSurfaceProvider();
75+
76+
void DropImageReference();
77+
void CheckForNewFrameAtYield();
78+
void CheckForNewFrameAtTerminalState();
79+
void AnnounceSurfaceAvailable();
80+
void FinishDecoding();
81+
82+
/// The image associated with our decoder.
83+
RefPtr<RasterImage> mImage;
84+
85+
/// A mutex to protect mDecoder. Always taken before mFramesMutex.
86+
mutable Mutex mDecodingMutex;
87+
88+
/// The decoder used to decode this animation.
89+
RefPtr<Decoder> mDecoder;
90+
91+
/// A mutex to protect mFrames. Always taken after mDecodingMutex.
92+
mutable Mutex mFramesMutex;
93+
94+
/// The frames of this animation, in order.
95+
nsTArray<RawAccessFrameRef> mFrames;
96+
97+
/// The key under which we're stored as a cache entry in the surface cache.
98+
const SurfaceKey mSurfaceKey;
99+
};
100+
101+
} // namespace image
102+
} // namespace mozilla
103+
104+
#endif // mozilla_image_AnimationSurfaceProvider_h

image/moz.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ EXPORTS += [
5050
]
5151

5252
UNIFIED_SOURCES += [
53+
'AnimationSurfaceProvider.cpp',
5354
'ClippedImage.cpp',
5455
'DecodedSurfaceProvider.cpp',
5556
'DecodePool.cpp',

0 commit comments

Comments
 (0)