Coverage Report

Created: 2018-09-25 14:53

/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