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 "DecodedSurfaceProvider.h"
7 
8 #include "mozilla/StaticPrefs_image.h"
9 #include "nsProxyRelease.h"
10 
11 #include "Decoder.h"
12 
13 using namespace mozilla::gfx;
14 
15 namespace mozilla {
16 namespace image {
17 
DecodedSurfaceProvider(NotNull<RasterImage * > aImage,const SurfaceKey & aSurfaceKey,NotNull<Decoder * > aDecoder)18 DecodedSurfaceProvider::DecodedSurfaceProvider(NotNull<RasterImage*> aImage,
19                                                const SurfaceKey& aSurfaceKey,
20                                                NotNull<Decoder*> aDecoder)
21     : ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey,
22                        AvailabilityState::StartAsPlaceholder()),
23       mImage(aImage.get()),
24       mMutex("mozilla::image::DecodedSurfaceProvider"),
25       mDecoder(aDecoder.get()) {
26   MOZ_ASSERT(!mDecoder->IsMetadataDecode(),
27              "Use MetadataDecodingTask for metadata decodes");
28   MOZ_ASSERT(mDecoder->IsFirstFrameDecode(),
29              "Use AnimationSurfaceProvider for animation decodes");
30 }
31 
~DecodedSurfaceProvider()32 DecodedSurfaceProvider::~DecodedSurfaceProvider() { DropImageReference(); }
33 
DropImageReference()34 void DecodedSurfaceProvider::DropImageReference() {
35   if (!mImage) {
36     return;  // Nothing to do.
37   }
38 
39   // RasterImage objects need to be destroyed on the main thread. We also need
40   // to destroy them asynchronously, because if our surface cache entry is
41   // destroyed and we were the only thing keeping |mImage| alive, RasterImage's
42   // destructor may call into the surface cache while whatever code caused us to
43   // get evicted is holding the surface cache lock, causing deadlock.
44   RefPtr<RasterImage> image = mImage;
45   mImage = nullptr;
46   SurfaceCache::ReleaseImageOnMainThread(image.forget(),
47                                          /* aAlwaysProxy = */ true);
48 }
49 
DrawableRef(size_t aFrame)50 DrawableFrameRef DecodedSurfaceProvider::DrawableRef(size_t aFrame) {
51   MOZ_ASSERT(aFrame == 0,
52              "Requesting an animation frame from a DecodedSurfaceProvider?");
53 
54   // We depend on SurfaceCache::SurfaceAvailable() to provide synchronization
55   // for methods that touch |mSurface|; after SurfaceAvailable() is called,
56   // |mSurface| should be non-null and shouldn't be mutated further until we get
57   // destroyed. That means that the assertions below are very important; we'll
58   // end up with data races if these assumptions are violated.
59   if (Availability().IsPlaceholder()) {
60     MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() on a placeholder");
61     return DrawableFrameRef();
62   }
63 
64   if (!mSurface) {
65     MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() when we have no surface");
66     return DrawableFrameRef();
67   }
68 
69   return mSurface->DrawableRef();
70 }
71 
IsFinished() const72 bool DecodedSurfaceProvider::IsFinished() const {
73   // See DrawableRef() for commentary on these assertions.
74   if (Availability().IsPlaceholder()) {
75     MOZ_ASSERT_UNREACHABLE("Calling IsFinished() on a placeholder");
76     return false;
77   }
78 
79   if (!mSurface) {
80     MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no surface");
81     return false;
82   }
83 
84   return mSurface->IsFinished();
85 }
86 
SetLocked(bool aLocked)87 void DecodedSurfaceProvider::SetLocked(bool aLocked) {
88   // See DrawableRef() for commentary on these assertions.
89   if (Availability().IsPlaceholder()) {
90     MOZ_ASSERT_UNREACHABLE("Calling SetLocked() on a placeholder");
91     return;
92   }
93 
94   if (!mSurface) {
95     MOZ_ASSERT_UNREACHABLE("Calling SetLocked() when we have no surface");
96     return;
97   }
98 
99   if (aLocked == IsLocked()) {
100     return;  // Nothing to do.
101   }
102 
103   // If we're locked, hold a DrawableFrameRef to |mSurface|, which will keep any
104   // volatile buffer it owns in memory.
105   mLockRef = aLocked ? mSurface->DrawableRef() : DrawableFrameRef();
106 }
107 
LogicalSizeInBytes() const108 size_t DecodedSurfaceProvider::LogicalSizeInBytes() const {
109   // Single frame images are always 32bpp.
110   IntSize size = GetSurfaceKey().Size();
111   return size_t(size.width) * size_t(size.height) * sizeof(uint32_t);
112 }
113 
Run()114 void DecodedSurfaceProvider::Run() {
115   MutexAutoLock lock(mMutex);
116 
117   if (!mDecoder || !mImage) {
118     MOZ_ASSERT_UNREACHABLE("Running after decoding finished?");
119     return;
120   }
121 
122   // Run the decoder.
123   LexerResult result = mDecoder->Decode(WrapNotNull(this));
124 
125   // If there's a new surface available, announce it to the surface cache.
126   CheckForNewSurface();
127 
128   if (result.is<TerminalState>()) {
129     FinishDecoding();
130     return;  // We're done.
131   }
132 
133   // Notify for the progress we've made so far.
134   if (mDecoder->HasProgress()) {
135     NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder));
136   }
137 
138   MOZ_ASSERT(result.is<Yield>());
139 
140   if (result == LexerResult(Yield::NEED_MORE_DATA)) {
141     // We can't make any more progress right now. The decoder itself will ensure
142     // that we get reenqueued when more data is available; just return for now.
143     return;
144   }
145 
146   // Single-frame images shouldn't yield for any reason except NEED_MORE_DATA.
147   MOZ_ASSERT_UNREACHABLE("Unexpected yield for single-frame image");
148   mDecoder->TerminateFailure();
149   FinishDecoding();
150 }
151 
CheckForNewSurface()152 void DecodedSurfaceProvider::CheckForNewSurface() {
153   mMutex.AssertCurrentThreadOwns();
154   MOZ_ASSERT(mDecoder);
155 
156   if (mSurface) {
157     // Single-frame images should produce no more than one surface, so if we
158     // have one, it should be the same one the decoder is working on.
159     MOZ_ASSERT(mSurface.get() == mDecoder->GetCurrentFrameRef().get(),
160                "DecodedSurfaceProvider and Decoder have different surfaces?");
161     return;
162   }
163 
164   // We don't have a surface yet; try to get one from the decoder.
165   mSurface = mDecoder->GetCurrentFrameRef().get();
166   if (!mSurface) {
167     return;  // No surface yet.
168   }
169 
170   // We just got a surface for the first time; let the surface cache know.
171   MOZ_ASSERT(mImage);
172   SurfaceCache::SurfaceAvailable(WrapNotNull(this));
173 }
174 
FinishDecoding()175 void DecodedSurfaceProvider::FinishDecoding() {
176   mMutex.AssertCurrentThreadOwns();
177   MOZ_ASSERT(mImage);
178   MOZ_ASSERT(mDecoder);
179 
180   // Send notifications.
181   NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
182 
183   // If we have a new and complete surface, we can try to prune similarly sized
184   // surfaces if the cache supports it.
185   if (mSurface && mSurface->IsFinished()) {
186     SurfaceCache::PruneImage(ImageKey(mImage));
187   }
188 
189   // Destroy our decoder; we don't need it anymore. (And if we don't destroy it,
190   // our surface can never be optimized, because the decoder has a
191   // RawAccessFrameRef to it.)
192   mDecoder = nullptr;
193 
194   // We don't need a reference to our image anymore, either, and we don't want
195   // one. We may be stored in the surface cache for a long time after decoding
196   // finishes. If we don't drop our reference to the image, we'll end up
197   // keeping it alive as long as we remain in the surface cache, which could
198   // greatly extend the image's lifetime - in fact, if the image isn't
199   // discardable, it'd result in a leak!
200   DropImageReference();
201 }
202 
ShouldPreferSyncRun() const203 bool DecodedSurfaceProvider::ShouldPreferSyncRun() const {
204   return mDecoder->ShouldSyncDecode(
205       StaticPrefs::image_mem_decode_bytes_at_a_time_AtStartup());
206 }
207 
208 }  // namespace image
209 }  // namespace mozilla
210