/src/mozilla-central/gfx/layers/composite/ImageComposite.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "ImageComposite.h" |
8 | | |
9 | | namespace mozilla { |
10 | | |
11 | | using namespace gfx; |
12 | | |
13 | | namespace layers { |
14 | | |
15 | | /* static */ const float ImageComposite::BIAS_TIME_MS = 1.0f; |
16 | | |
17 | | ImageComposite::ImageComposite() |
18 | | : mLastFrameID(-1) |
19 | | , mLastProducerID(-1) |
20 | | , mBias(BIAS_NONE) |
21 | | , mDroppedFrames(0) |
22 | | , mLastChosenImageIndex(0) |
23 | 0 | { |
24 | 0 | } |
25 | | |
26 | | ImageComposite::~ImageComposite() |
27 | 0 | { |
28 | 0 | } |
29 | | |
30 | | TimeStamp |
31 | | ImageComposite::GetBiasedTime(const TimeStamp& aInput) const |
32 | | { |
33 | | switch (mBias) { |
34 | | case ImageComposite::BIAS_NEGATIVE: |
35 | | return aInput - TimeDuration::FromMilliseconds(BIAS_TIME_MS); |
36 | | case ImageComposite::BIAS_POSITIVE: |
37 | | return aInput + TimeDuration::FromMilliseconds(BIAS_TIME_MS); |
38 | | default: |
39 | | return aInput; |
40 | | } |
41 | | } |
42 | | |
43 | | void |
44 | | ImageComposite::UpdateBias(size_t aImageIndex) |
45 | 0 | { |
46 | 0 | MOZ_ASSERT(aImageIndex < ImagesCount()); |
47 | 0 |
|
48 | 0 | TimeStamp compositionTime = GetCompositionTime(); |
49 | 0 | TimeStamp compositedImageTime = mImages[aImageIndex].mTimeStamp; |
50 | 0 | TimeStamp nextImageTime = aImageIndex + 1 < ImagesCount() |
51 | 0 | ? mImages[aImageIndex + 1].mTimeStamp |
52 | 0 | : TimeStamp(); |
53 | 0 |
|
54 | 0 | if (compositedImageTime.IsNull()) { |
55 | 0 | mBias = ImageComposite::BIAS_NONE; |
56 | 0 | return; |
57 | 0 | } |
58 | 0 | TimeDuration threshold = TimeDuration::FromMilliseconds(1.0); |
59 | 0 | if (compositionTime - compositedImageTime < threshold && |
60 | 0 | compositionTime - compositedImageTime > -threshold) { |
61 | 0 | // The chosen frame's time is very close to the composition time (probably |
62 | 0 | // just before the current composition time, but due to previously set |
63 | 0 | // negative bias, it could be just after the current composition time too). |
64 | 0 | // If the inter-frame time is almost exactly equal to (a multiple of) |
65 | 0 | // the inter-composition time, then we're in a dangerous situation because |
66 | 0 | // jitter might cause frames to fall one side or the other of the |
67 | 0 | // composition times, causing many frames to be skipped or duplicated. |
68 | 0 | // Try to prevent that by adding a negative bias to the frame times during |
69 | 0 | // the next composite; that should ensure the next frame's time is treated |
70 | 0 | // as falling just before a composite time. |
71 | 0 | mBias = ImageComposite::BIAS_NEGATIVE; |
72 | 0 | return; |
73 | 0 | } |
74 | 0 | if (!nextImageTime.IsNull() && |
75 | 0 | nextImageTime - compositionTime < threshold && |
76 | 0 | nextImageTime - compositionTime > -threshold) { |
77 | 0 | // The next frame's time is very close to our composition time (probably |
78 | 0 | // just after the current composition time, but due to previously set |
79 | 0 | // positive bias, it could be just before the current composition time too). |
80 | 0 | // We're in a dangerous situation because jitter might cause frames to |
81 | 0 | // fall one side or the other of the composition times, causing many frames |
82 | 0 | // to be skipped or duplicated. |
83 | 0 | // Try to prevent that by adding a negative bias to the frame times during |
84 | 0 | // the next composite; that should ensure the next frame's time is treated |
85 | 0 | // as falling just before a composite time. |
86 | 0 | mBias = ImageComposite::BIAS_POSITIVE; |
87 | 0 | return; |
88 | 0 | } |
89 | 0 | mBias = ImageComposite::BIAS_NONE; |
90 | 0 | } |
91 | | |
92 | | int |
93 | | ImageComposite::ChooseImageIndex() |
94 | 0 | { |
95 | 0 | // ChooseImageIndex is called for all images in the layer when it is visible. |
96 | 0 | // Change to this behaviour would break dropped frames counting calculation: |
97 | 0 | // We rely on this assumption to determine if during successive runs an |
98 | 0 | // image is returned that isn't the one following immediately the previous one |
99 | 0 | if (mImages.IsEmpty()) { |
100 | 0 | return -1; |
101 | 0 | } |
102 | 0 | TimeStamp now = GetCompositionTime(); |
103 | 0 |
|
104 | 0 | if (now.IsNull()) { |
105 | 0 | // Not in a composition, so just return the last image we composited |
106 | 0 | // (if it's one of the current images). |
107 | 0 | for (uint32_t i = 0; i < mImages.Length(); ++i) { |
108 | 0 | if (mImages[i].mFrameID == mLastFrameID && |
109 | 0 | mImages[i].mProducerID == mLastProducerID) { |
110 | 0 | return i; |
111 | 0 | } |
112 | 0 | } |
113 | 0 | return -1; |
114 | 0 | } |
115 | 0 | |
116 | 0 | uint32_t result = mLastChosenImageIndex; |
117 | 0 | while (result + 1 < mImages.Length() && |
118 | 0 | GetBiasedTime(mImages[result + 1].mTimeStamp) <= now) { |
119 | 0 | ++result; |
120 | 0 | } |
121 | 0 | if (result - mLastChosenImageIndex > 1) { |
122 | 0 | // We're not returning the same image as the last call to ChooseImageIndex |
123 | 0 | // or the immediately next one. We can assume that the frames not returned |
124 | 0 | // have been dropped as they were too late to be displayed |
125 | 0 | mDroppedFrames += result - mLastChosenImageIndex - 1; |
126 | 0 | } |
127 | 0 | mLastChosenImageIndex = result; |
128 | 0 | return result; |
129 | 0 | } |
130 | | |
131 | | const ImageComposite::TimedImage* ImageComposite::ChooseImage() |
132 | 0 | { |
133 | 0 | int index = ChooseImageIndex(); |
134 | 0 | return index >= 0 ? &mImages[index] : nullptr; |
135 | 0 | } |
136 | | |
137 | | void |
138 | | ImageComposite::RemoveImagesWithTextureHost(TextureHost* aTexture) |
139 | 0 | { |
140 | 0 | for (int32_t i = mImages.Length() - 1; i >= 0; --i) { |
141 | 0 | if (mImages[i].mTextureHost == aTexture) { |
142 | 0 | aTexture->UnbindTextureSource(); |
143 | 0 | mImages.RemoveElementAt(i); |
144 | 0 | } |
145 | 0 | } |
146 | 0 | } |
147 | | |
148 | | void |
149 | | ImageComposite::ClearImages() |
150 | 0 | { |
151 | 0 | mImages.Clear(); |
152 | 0 | mLastChosenImageIndex = 0; |
153 | 0 | } |
154 | | |
155 | | uint32_t |
156 | | ImageComposite::ScanForLastFrameIndex(const nsTArray<TimedImage>& aNewImages) |
157 | 0 | { |
158 | 0 | if (mImages.IsEmpty()) { |
159 | 0 | return 0; |
160 | 0 | } |
161 | 0 | uint32_t i = mLastChosenImageIndex; |
162 | 0 | uint32_t newIndex = 0; |
163 | 0 | uint32_t dropped = 0; |
164 | 0 | // See if the new array of images have any images in common with the |
165 | 0 | // previous list that we haven't played yet. |
166 | 0 | uint32_t j = 0; |
167 | 0 | while (i < mImages.Length() && j < aNewImages.Length()) { |
168 | 0 | if (mImages[i].mProducerID != aNewImages[j].mProducerID) { |
169 | 0 | // This is new content, can stop. |
170 | 0 | newIndex = j; |
171 | 0 | break; |
172 | 0 | } |
173 | 0 | int32_t oldFrameID = mImages[i].mFrameID; |
174 | 0 | int32_t newFrameID = aNewImages[j].mFrameID; |
175 | 0 | if (oldFrameID > newFrameID) { |
176 | 0 | // This is an image we have already returned, we don't need to present |
177 | 0 | // it again and can start from this index next time. |
178 | 0 | newIndex = ++j; |
179 | 0 | continue; |
180 | 0 | } |
181 | 0 | if (oldFrameID < mLastFrameID) { |
182 | 0 | // we have already returned that frame previously, ignore. |
183 | 0 | i++; |
184 | 0 | continue; |
185 | 0 | } |
186 | 0 | if (oldFrameID < newFrameID) { |
187 | 0 | // This is a new image, all images prior the new one and not yet |
188 | 0 | // rendered can be considered as dropped. Those images have a FrameID |
189 | 0 | // inferior to the new image. |
190 | 0 | for (++i; i < mImages.Length() && mImages[i].mFrameID < newFrameID && |
191 | 0 | mImages[i].mProducerID == aNewImages[j].mProducerID; |
192 | 0 | i++) { |
193 | 0 | dropped++; |
194 | 0 | } |
195 | 0 | break; |
196 | 0 | } |
197 | 0 | i++; |
198 | 0 | j++; |
199 | 0 | } |
200 | 0 | if (dropped > 0) { |
201 | 0 | mDroppedFrames += dropped; |
202 | 0 | } |
203 | 0 | if (newIndex >= aNewImages.Length()) { |
204 | 0 | // Somehow none of those images should be rendered (can this happen?) |
205 | 0 | // We will always return the last one for now. |
206 | 0 | newIndex = aNewImages.Length() - 1; |
207 | 0 | } |
208 | 0 | return newIndex; |
209 | 0 | } |
210 | | |
211 | | void |
212 | | ImageComposite::SetImages(nsTArray<TimedImage>&& aNewImages) |
213 | 0 | { |
214 | 0 | mLastChosenImageIndex = ScanForLastFrameIndex(aNewImages); |
215 | 0 | mImages = std::move(aNewImages); |
216 | 0 | } |
217 | | |
218 | | const ImageComposite::TimedImage* |
219 | | ImageComposite::GetImage(size_t aIndex) const |
220 | 0 | { |
221 | 0 | if (aIndex >= mImages.Length()) { |
222 | 0 | return nullptr; |
223 | 0 | } |
224 | 0 | return &mImages[aIndex]; |
225 | 0 | } |
226 | | |
227 | | } // namespace layers |
228 | | } // namespace mozilla |