/src/mozilla-central/image/AnimationFrameBuffer.cpp
Line | Count | Source (jump to first uncovered line) |
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 "AnimationFrameBuffer.h" |
7 | | #include "mozilla/Move.h" // for Move |
8 | | |
9 | | namespace mozilla { |
10 | | namespace image { |
11 | | |
12 | | AnimationFrameBuffer::AnimationFrameBuffer() |
13 | | : mThreshold(0) |
14 | | , mBatch(0) |
15 | | , mPending(0) |
16 | | , mAdvance(0) |
17 | | , mInsertIndex(0) |
18 | | , mGetIndex(0) |
19 | | , mSizeKnown(false) |
20 | | , mRedecodeError(false) |
21 | 0 | { } |
22 | | |
23 | | void |
24 | | AnimationFrameBuffer::Initialize(size_t aThreshold, |
25 | | size_t aBatch, |
26 | | size_t aStartFrame) |
27 | 0 | { |
28 | 0 | MOZ_ASSERT(mThreshold == 0); |
29 | 0 | MOZ_ASSERT(mBatch == 0); |
30 | 0 | MOZ_ASSERT(mPending == 0); |
31 | 0 | MOZ_ASSERT(mAdvance == 0); |
32 | 0 | MOZ_ASSERT(mFrames.IsEmpty()); |
33 | 0 |
|
34 | 0 | mThreshold = aThreshold; |
35 | 0 | mBatch = aBatch; |
36 | 0 | mAdvance = aStartFrame; |
37 | 0 |
|
38 | 0 | if (mBatch > SIZE_MAX/4) { |
39 | 0 | // Batch size is so big, we will just end up decoding the whole animation. |
40 | 0 | mBatch = SIZE_MAX/4; |
41 | 0 | } else if (mBatch < 1) { |
42 | 0 | // Never permit a batch size smaller than 1. We always want to be asking for |
43 | 0 | // at least one frame to start. |
44 | 0 | mBatch = 1; |
45 | 0 | } |
46 | 0 |
|
47 | 0 | // To simplify the code, we have the assumption that the threshold for |
48 | 0 | // entering discard-after-display mode is at least twice the batch size (since |
49 | 0 | // that is the most frames-pending-decode we will request) + 1 for the current |
50 | 0 | // frame. That way the redecoded frames being inserted will never risk |
51 | 0 | // overlapping the frames we will discard due to the animation progressing. |
52 | 0 | // That may cause us to use a little more memory than we want but that is an |
53 | 0 | // acceptable tradeoff for simplicity. |
54 | 0 | size_t minThreshold = 2 * mBatch + 1; |
55 | 0 | if (mThreshold < minThreshold) { |
56 | 0 | mThreshold = minThreshold; |
57 | 0 | } |
58 | 0 |
|
59 | 0 | // The maximum number of frames we should ever have decoded at one time is |
60 | 0 | // twice the batch. That is a good as number as any to start our decoding at. |
61 | 0 | mPending = mBatch * 2; |
62 | 0 | } |
63 | | |
64 | | bool |
65 | | AnimationFrameBuffer::Insert(RawAccessFrameRef&& aFrame) |
66 | 0 | { |
67 | 0 | // We should only insert new frames if we actually asked for them. |
68 | 0 | MOZ_ASSERT(mPending > 0); |
69 | 0 |
|
70 | 0 | if (mSizeKnown) { |
71 | 0 | // We only insert after the size is known if we are repeating the animation |
72 | 0 | // and we did not keep all of the frames. Replace whatever is there |
73 | 0 | // (probably an empty frame) with the new frame. |
74 | 0 | MOZ_ASSERT(MayDiscard()); |
75 | 0 |
|
76 | 0 | // The first decode produced fewer frames than the redecodes, presumably |
77 | 0 | // because it hit an out-of-memory error which later attempts avoided. Just |
78 | 0 | // stop the animation because we can't tell the image that we have more |
79 | 0 | // frames now. |
80 | 0 | if (mInsertIndex >= mFrames.Length()) { |
81 | 0 | mRedecodeError = true; |
82 | 0 | mPending = 0; |
83 | 0 | return false; |
84 | 0 | } |
85 | 0 | |
86 | 0 | if (mInsertIndex > 0) { |
87 | 0 | MOZ_ASSERT(!mFrames[mInsertIndex]); |
88 | 0 | mFrames[mInsertIndex] = std::move(aFrame); |
89 | 0 | } |
90 | 0 | } else if (mInsertIndex == mFrames.Length()) { |
91 | 0 | // We are still on the first pass of the animation decoding, so this is |
92 | 0 | // the first time we have seen this frame. |
93 | 0 | mFrames.AppendElement(std::move(aFrame)); |
94 | 0 |
|
95 | 0 | if (mInsertIndex == mThreshold) { |
96 | 0 | // We just tripped over the threshold for the first time. This is our |
97 | 0 | // chance to do any clearing of already displayed frames. After this, |
98 | 0 | // we only need to release as we advance or force a restart. |
99 | 0 | MOZ_ASSERT(MayDiscard()); |
100 | 0 | MOZ_ASSERT(mGetIndex < mInsertIndex); |
101 | 0 | for (size_t i = 1; i < mGetIndex; ++i) { |
102 | 0 | RawAccessFrameRef discard = std::move(mFrames[i]); |
103 | 0 | } |
104 | 0 | } |
105 | 0 | } else if (mInsertIndex > 0) { |
106 | 0 | // We were forced to restart an animation before we decoded the last |
107 | 0 | // frame. If we were discarding frames, then we tossed what we had |
108 | 0 | // except for the first frame. |
109 | 0 | MOZ_ASSERT(mInsertIndex < mFrames.Length()); |
110 | 0 | MOZ_ASSERT(!mFrames[mInsertIndex]); |
111 | 0 | MOZ_ASSERT(MayDiscard()); |
112 | 0 | mFrames[mInsertIndex] = std::move(aFrame); |
113 | 0 | } else { // mInsertIndex == 0 |
114 | 0 | // We were forced to restart an animation before we decoded the last |
115 | 0 | // frame. We don't need the redecoded first frame because we always keep |
116 | 0 | // the original. |
117 | 0 | MOZ_ASSERT(MayDiscard()); |
118 | 0 | } |
119 | 0 |
|
120 | 0 | MOZ_ASSERT(mFrames[mInsertIndex]); |
121 | 0 | ++mInsertIndex; |
122 | 0 |
|
123 | 0 | // Ensure we only request more decoded frames if we actually need them. If we |
124 | 0 | // need to advance to a certain point in the animation on behalf of the owner, |
125 | 0 | // then do so. This ensures we keep decoding. If the batch size is really |
126 | 0 | // small (i.e. 1), it is possible advancing will request the decoder to |
127 | 0 | // "restart", but we haven't told it to stop yet. Note that we skip the first |
128 | 0 | // insert because we actually start "advanced" to the first frame anyways. |
129 | 0 | bool continueDecoding = --mPending > 0; |
130 | 0 | if (mAdvance > 0 && mInsertIndex > 1) { |
131 | 0 | continueDecoding |= AdvanceInternal(); |
132 | 0 | --mAdvance; |
133 | 0 | } |
134 | 0 | return continueDecoding; |
135 | 0 | } |
136 | | |
137 | | bool |
138 | | AnimationFrameBuffer::MarkComplete() |
139 | 0 | { |
140 | 0 | // We may have stopped decoding at a different point in the animation than we |
141 | 0 | // did previously. That means the decoder likely hit a new error, e.g. OOM. |
142 | 0 | // This will prevent us from advancing as well, because we are missing the |
143 | 0 | // required frames to blend. |
144 | 0 | // |
145 | 0 | // XXX(aosmond): In an ideal world, we would be generating full frames, and |
146 | 0 | // the consumer of our data doesn't care about our internal state. It simply |
147 | 0 | // knows about the first frame, the current frame, and how long to display the |
148 | 0 | // current frame. |
149 | 0 | if (NS_WARN_IF(mInsertIndex != mFrames.Length())) { |
150 | 0 | MOZ_ASSERT(mSizeKnown); |
151 | 0 | mRedecodeError = true; |
152 | 0 | mPending = 0; |
153 | 0 | } |
154 | 0 |
|
155 | 0 | // We reached the end of the animation, the next frame we get, if we get |
156 | 0 | // another, will be the first frame again. |
157 | 0 | mInsertIndex = 0; |
158 | 0 |
|
159 | 0 | // Since we only request advancing when we want to resume at a certain point |
160 | 0 | // in the animation, we should never exceed the number of frames. |
161 | 0 | MOZ_ASSERT(mAdvance == 0); |
162 | 0 |
|
163 | 0 | if (!mSizeKnown) { |
164 | 0 | // We just received the last frame in the animation. Compact the frame array |
165 | 0 | // because we know we won't need to grow beyond here. |
166 | 0 | mSizeKnown = true; |
167 | 0 | mFrames.Compact(); |
168 | 0 |
|
169 | 0 | if (!MayDiscard()) { |
170 | 0 | // If we did not meet the threshold, then we know we want to keep all of the |
171 | 0 | // frames. If we also hit the last frame, we don't want to ask for more. |
172 | 0 | mPending = 0; |
173 | 0 | } |
174 | 0 | } |
175 | 0 |
|
176 | 0 | return mPending > 0; |
177 | 0 | } |
178 | | |
179 | | imgFrame* |
180 | | AnimationFrameBuffer::Get(size_t aFrame) |
181 | 0 | { |
182 | 0 | // We should not have asked for a frame if we never inserted. |
183 | 0 | if (mFrames.IsEmpty()) { |
184 | 0 | MOZ_ASSERT_UNREACHABLE("Calling Get() when we have no frames"); |
185 | 0 | return nullptr; |
186 | 0 | } |
187 | 0 |
|
188 | 0 | // If we don't have that frame, return an empty frame ref. |
189 | 0 | if (aFrame >= mFrames.Length()) { |
190 | 0 | return nullptr; |
191 | 0 | } |
192 | 0 | |
193 | 0 | // We've got the requested frame because we are not discarding frames. While |
194 | 0 | // we typically should have not run out of frames since we ask for more before |
195 | 0 | // we want them, it is possible the decoder is behind. |
196 | 0 | if (!mFrames[aFrame]) { |
197 | 0 | MOZ_ASSERT(MayDiscard()); |
198 | 0 | return nullptr; |
199 | 0 | } |
200 | 0 |
|
201 | 0 | // If we are advancing on behalf of the animation, we don't expect it to be |
202 | 0 | // getting any frames (besides the first) until we get the desired frame. |
203 | 0 | MOZ_ASSERT(aFrame == 0 || mAdvance == 0); |
204 | 0 | return mFrames[aFrame].get(); |
205 | 0 | } |
206 | | |
207 | | bool |
208 | | AnimationFrameBuffer::AdvanceTo(size_t aExpectedFrame) |
209 | 0 | { |
210 | 0 | // The owner should only be advancing once it has reached the requested frame |
211 | 0 | // in the animation. |
212 | 0 | MOZ_ASSERT(mAdvance == 0); |
213 | 0 | bool restartDecoder = AdvanceInternal(); |
214 | 0 | // Advancing should always be successful, as it should only happen after the |
215 | 0 | // owner has accessed the next (now current) frame. |
216 | 0 | MOZ_ASSERT(mGetIndex == aExpectedFrame); |
217 | 0 | return restartDecoder; |
218 | 0 | } |
219 | | |
220 | | bool |
221 | | AnimationFrameBuffer::AdvanceInternal() |
222 | 0 | { |
223 | 0 | // We should not have advanced if we never inserted. |
224 | 0 | if (mFrames.IsEmpty()) { |
225 | 0 | MOZ_ASSERT_UNREACHABLE("Calling Advance() when we have no frames"); |
226 | 0 | return false; |
227 | 0 | } |
228 | 0 |
|
229 | 0 | // We only want to change the current frame index if we have advanced. This |
230 | 0 | // means either a higher frame index, or going back to the beginning. |
231 | 0 | size_t framesLength = mFrames.Length(); |
232 | 0 | // We should never have advanced beyond the frame buffer. |
233 | 0 | MOZ_ASSERT(mGetIndex < framesLength); |
234 | 0 | // We should never advance if the current frame is null -- it needs to know |
235 | 0 | // the timeout from it at least to know when to advance. |
236 | 0 | MOZ_ASSERT(mFrames[mGetIndex]); |
237 | 0 | if (++mGetIndex == framesLength) { |
238 | 0 | MOZ_ASSERT(mSizeKnown); |
239 | 0 | mGetIndex = 0; |
240 | 0 | } |
241 | 0 | // The owner should have already accessed the next frame, so it should also |
242 | 0 | // be available. |
243 | 0 | MOZ_ASSERT(mFrames[mGetIndex]); |
244 | 0 |
|
245 | 0 | // If we moved forward, that means we can remove the previous frame, assuming |
246 | 0 | // that frame is not the first frame. If we looped and are back at the first |
247 | 0 | // frame, we can remove the last frame. |
248 | 0 | if (MayDiscard()) { |
249 | 0 | RawAccessFrameRef discard; |
250 | 0 | if (mGetIndex > 1) { |
251 | 0 | discard = std::move(mFrames[mGetIndex - 1]); |
252 | 0 | } else if (mGetIndex == 0) { |
253 | 0 | MOZ_ASSERT(mSizeKnown && framesLength > 1); |
254 | 0 | discard = std::move(mFrames[framesLength - 1]); |
255 | 0 | } |
256 | 0 | } |
257 | 0 |
|
258 | 0 | if (!mRedecodeError && (!mSizeKnown || MayDiscard())) { |
259 | 0 | // Calculate how many frames we have requested ahead of the current frame. |
260 | 0 | size_t buffered = mPending; |
261 | 0 | if (mGetIndex > mInsertIndex) { |
262 | 0 | // It wrapped around and we are decoding the beginning again before the |
263 | 0 | // the display has finished the loop. |
264 | 0 | MOZ_ASSERT(mSizeKnown); |
265 | 0 | buffered += mInsertIndex + framesLength - mGetIndex - 1; |
266 | 0 | } else { |
267 | 0 | buffered += mInsertIndex - mGetIndex - 1; |
268 | 0 | } |
269 | 0 |
|
270 | 0 | if (buffered < mBatch) { |
271 | 0 | // If we have fewer frames than the batch size, then ask for more. If we |
272 | 0 | // do not have any pending, then we know that there is no active decoding. |
273 | 0 | mPending += mBatch; |
274 | 0 | return mPending == mBatch; |
275 | 0 | } |
276 | 0 | } |
277 | 0 | |
278 | 0 | return false; |
279 | 0 | } |
280 | | |
281 | | bool |
282 | | AnimationFrameBuffer::Reset() |
283 | 0 | { |
284 | 0 | // The animation needs to start back at the beginning. |
285 | 0 | mGetIndex = 0; |
286 | 0 | mAdvance = 0; |
287 | 0 |
|
288 | 0 | if (!MayDiscard()) { |
289 | 0 | // If we haven't crossed the threshold, then we know by definition we have |
290 | 0 | // not discarded any frames. If we previously requested more frames, but |
291 | 0 | // it would have been more than we would have buffered otherwise, we can |
292 | 0 | // stop the decoding after one more frame. |
293 | 0 | if (mPending > 1 && mInsertIndex - 1 >= mBatch * 2) { |
294 | 0 | MOZ_ASSERT(!mSizeKnown); |
295 | 0 | mPending = 1; |
296 | 0 | } |
297 | 0 |
|
298 | 0 | // Either the decoder is still running, or we have enough frames already. |
299 | 0 | // No need for us to restart it. |
300 | 0 | return false; |
301 | 0 | } |
302 | 0 |
|
303 | 0 | // Discard all frames besides the first, because the decoder always expects |
304 | 0 | // that when it re-inserts a frame, it is not present. (It doesn't re-insert |
305 | 0 | // the first frame.) |
306 | 0 | for (size_t i = 1; i < mFrames.Length(); ++i) { |
307 | 0 | RawAccessFrameRef discard = std::move(mFrames[i]); |
308 | 0 | } |
309 | 0 |
|
310 | 0 | mInsertIndex = 0; |
311 | 0 |
|
312 | 0 | // If we hit an error after redecoding, we never want to restart decoding. |
313 | 0 | if (mRedecodeError) { |
314 | 0 | MOZ_ASSERT(mPending == 0); |
315 | 0 | return false; |
316 | 0 | } |
317 | 0 |
|
318 | 0 | bool restartDecoder = mPending == 0; |
319 | 0 | mPending = 2 * mBatch; |
320 | 0 | return restartDecoder; |
321 | 0 | } |
322 | | |
323 | | } // namespace image |
324 | | } // namespace mozilla |