OLD | NEW |
1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "content/browser/media/capture/animated_content_sampler.h" | 5 #include "content/browser/media/capture/animated_content_sampler.h" |
6 | 6 |
7 #include <cstdlib> | 7 #include <cmath> |
8 #include <utility> | 8 #include <utility> |
9 #include <vector> | 9 #include <vector> |
10 | 10 |
11 #include "base/logging.h" | 11 #include "base/logging.h" |
12 #include "base/memory/scoped_ptr.h" | 12 #include "base/memory/scoped_ptr.h" |
13 #include "base/time/time.h" | 13 #include "base/time/time.h" |
14 #include "testing/gtest/include/gtest/gtest.h" | 14 #include "testing/gtest/include/gtest/gtest.h" |
15 #include "ui/gfx/geometry/rect.h" | 15 #include "ui/gfx/geometry/rect.h" |
16 | 16 |
17 namespace content { | 17 namespace content { |
18 | 18 |
19 namespace { | 19 namespace { |
20 | 20 |
21 base::TimeTicks InitialTestTimeTicks() { | 21 base::TimeTicks InitialTestTimeTicks() { |
22 return base::TimeTicks() + base::TimeDelta::FromSeconds(1); | 22 return base::TimeTicks() + base::TimeDelta::FromSeconds(1); |
23 } | 23 } |
24 | 24 |
| 25 base::TimeDelta FpsAsPeriod(int frame_rate) { |
| 26 return base::TimeDelta::FromSeconds(1) / frame_rate; |
| 27 } |
| 28 |
25 } // namespace | 29 } // namespace |
26 | 30 |
27 class AnimatedContentSamplerTest : public ::testing::Test { | 31 class AnimatedContentSamplerTest : public ::testing::Test { |
28 public: | 32 public: |
29 AnimatedContentSamplerTest() {} | 33 AnimatedContentSamplerTest() {} |
30 ~AnimatedContentSamplerTest() override {} | 34 ~AnimatedContentSamplerTest() override {} |
31 | 35 |
32 void SetUp() override { | 36 void SetUp() override { |
33 const base::TimeDelta since_epoch = | 37 const base::TimeDelta since_epoch = |
34 InitialTestTimeTicks() - base::TimeTicks::UnixEpoch(); | 38 InitialTestTimeTicks() - base::TimeTicks::UnixEpoch(); |
(...skipping 30 matching lines...) Expand all Loading... |
65 // ElectMajorityDamageRect(). | 69 // ElectMajorityDamageRect(). |
66 void ObserveDamageRect(const gfx::Rect& damage_rect) { | 70 void ObserveDamageRect(const gfx::Rect& damage_rect) { |
67 sampler_->observations_.push_back( | 71 sampler_->observations_.push_back( |
68 AnimatedContentSampler::Observation(damage_rect, base::TimeTicks())); | 72 AnimatedContentSampler::Observation(damage_rect, base::TimeTicks())); |
69 } | 73 } |
70 | 74 |
71 gfx::Rect ElectMajorityDamageRect() const { | 75 gfx::Rect ElectMajorityDamageRect() const { |
72 return sampler_->ElectMajorityDamageRect(); | 76 return sampler_->ElectMajorityDamageRect(); |
73 } | 77 } |
74 | 78 |
| 79 static base::TimeDelta ComputeSamplingPeriod( |
| 80 base::TimeDelta detected_period, |
| 81 base::TimeDelta target_sampling_period, |
| 82 base::TimeDelta min_capture_period) { |
| 83 return AnimatedContentSampler::ComputeSamplingPeriod( |
| 84 detected_period, target_sampling_period, min_capture_period); |
| 85 } |
| 86 |
75 private: | 87 private: |
76 // Note: Not using base::RandInt() because it is horribly slow on debug | 88 // Note: Not using base::RandInt() because it is horribly slow on debug |
77 // builds. The following is a very simple, deterministic LCG: | 89 // builds. The following is a very simple, deterministic LCG: |
78 int NextRandomInt() { | 90 int NextRandomInt() { |
79 rand_seed_ = (1103515245 * rand_seed_ + 12345) % (1 << 31); | 91 rand_seed_ = (1103515245 * rand_seed_ + 12345) % (1 << 31); |
80 return rand_seed_; | 92 return rand_seed_; |
81 } | 93 } |
82 | 94 |
83 int rand_seed_; | 95 int rand_seed_; |
84 scoped_ptr<AnimatedContentSampler> sampler_; | 96 scoped_ptr<AnimatedContentSampler> sampler_; |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
155 ObserveDamageRect(spinner_rect); | 167 ObserveDamageRect(spinner_rect); |
156 ObserveDamageRect(video_rect); | 168 ObserveDamageRect(video_rect); |
157 ObserveDamageRect(spinner_rect); | 169 ObserveDamageRect(spinner_rect); |
158 ObserveDamageRect(spinner_rect); | 170 ObserveDamageRect(spinner_rect); |
159 ObserveDamageRect(video_rect); | 171 ObserveDamageRect(video_rect); |
160 ObserveDamageRect(spinner_rect); | 172 ObserveDamageRect(spinner_rect); |
161 } | 173 } |
162 EXPECT_EQ(video_rect, ElectMajorityDamageRect()); | 174 EXPECT_EQ(video_rect, ElectMajorityDamageRect()); |
163 } | 175 } |
164 | 176 |
| 177 TEST_F(AnimatedContentSamplerTest, TargetsSamplingPeriod) { |
| 178 const base::TimeDelta min_capture_period = FpsAsPeriod(60); |
| 179 const int target_fps = 30; |
| 180 const base::TimeDelta target_sampling_period = FpsAsPeriod(target_fps); |
| 181 |
| 182 for (int content_fps = 1; content_fps <= 60; ++content_fps) { |
| 183 const base::TimeDelta content_period = FpsAsPeriod(content_fps); |
| 184 const base::TimeDelta sampling_period = |
| 185 ComputeSamplingPeriod(content_period, |
| 186 target_sampling_period, |
| 187 min_capture_period); |
| 188 if (content_period >= target_sampling_period) { |
| 189 ASSERT_EQ(content_period, sampling_period); |
| 190 } else { |
| 191 ASSERT_LE(min_capture_period, sampling_period); |
| 192 |
| 193 // Check that the sampling rate is as close (or closer) to the target |
| 194 // sampling rate than any integer-subsampling of the content frame rate. |
| 195 const double absolute_diff = |
| 196 std::abs(1.0 / sampling_period.InSecondsF() - target_fps); |
| 197 const double fudge_for_acceptable_rounding_error = 0.005; |
| 198 for (double divisor = 1; divisor < 4; ++divisor) { |
| 199 SCOPED_TRACE(::testing::Message() << "content_fps=" << content_fps |
| 200 << ", divisor=" << divisor); |
| 201 ASSERT_GE(std::abs(content_fps / divisor - target_fps), |
| 202 absolute_diff - fudge_for_acceptable_rounding_error); |
| 203 } |
| 204 } |
| 205 } |
| 206 } |
| 207 |
165 namespace { | 208 namespace { |
166 | 209 |
167 // A test scenario for AnimatedContentSamplerParameterizedTest. | 210 // A test scenario for AnimatedContentSamplerParameterizedTest. |
168 struct Scenario { | 211 struct Scenario { |
169 base::TimeDelta vsync_interval; // Reflects compositor's update rate. | 212 base::TimeDelta vsync_interval; // Reflects compositor's update rate. |
170 base::TimeDelta min_capture_period; // Reflects maximum capture rate. | 213 base::TimeDelta min_capture_period; // Reflects maximum capture rate. |
171 base::TimeDelta content_period; // Reflects content animation rate. | 214 base::TimeDelta content_period; // Reflects content animation rate. |
| 215 base::TimeDelta target_sampling_period; |
172 | 216 |
173 Scenario(base::TimeDelta v, base::TimeDelta m, base::TimeDelta c) | 217 Scenario(int compositor_frequency, |
174 : vsync_interval(v), min_capture_period(m), content_period(c) { | 218 int max_frame_rate, |
| 219 int content_frame_rate) |
| 220 : vsync_interval(FpsAsPeriod(compositor_frequency)), |
| 221 min_capture_period(FpsAsPeriod(max_frame_rate)), |
| 222 content_period(FpsAsPeriod(content_frame_rate)) { |
175 CHECK(content_period >= vsync_interval) | 223 CHECK(content_period >= vsync_interval) |
176 << "Bad test params: Impossible to animate faster than the compositor."; | 224 << "Bad test params: Impossible to animate faster than the compositor."; |
177 } | 225 } |
| 226 |
| 227 Scenario(int compositor_frequency, |
| 228 int max_frame_rate, |
| 229 int content_frame_rate, |
| 230 int target_sampling_rate) |
| 231 : vsync_interval(FpsAsPeriod(compositor_frequency)), |
| 232 min_capture_period(FpsAsPeriod(max_frame_rate)), |
| 233 content_period(FpsAsPeriod(content_frame_rate)), |
| 234 target_sampling_period(FpsAsPeriod(target_sampling_rate)) { |
| 235 CHECK(content_period >= vsync_interval) |
| 236 << "Bad test params: Impossible to animate faster than the compositor."; |
| 237 } |
178 }; | 238 }; |
179 | 239 |
180 // Value printer for Scenario. | 240 // Value printer for Scenario. |
181 ::std::ostream& operator<<(::std::ostream& os, const Scenario& s) { | 241 ::std::ostream& operator<<(::std::ostream& os, const Scenario& s) { |
182 return os << "{ vsync_interval=" << s.vsync_interval.InMicroseconds() | 242 return os << "{ vsync_interval=" << s.vsync_interval.InMicroseconds() |
183 << ", min_capture_period=" << s.min_capture_period.InMicroseconds() | 243 << ", min_capture_period=" << s.min_capture_period.InMicroseconds() |
184 << ", content_period=" << s.content_period.InMicroseconds() | 244 << ", content_period=" << s.content_period.InMicroseconds() |
185 << " }"; | 245 << " }"; |
186 } | 246 } |
187 | 247 |
188 base::TimeDelta FpsAsPeriod(int frame_rate) { | |
189 return base::TimeDelta::FromSeconds(1) / frame_rate; | |
190 } | |
191 | |
192 } // namespace | 248 } // namespace |
193 | 249 |
194 class AnimatedContentSamplerParameterizedTest | 250 class AnimatedContentSamplerParameterizedTest |
195 : public AnimatedContentSamplerTest, | 251 : public AnimatedContentSamplerTest, |
196 public ::testing::WithParamInterface<Scenario> { | 252 public ::testing::WithParamInterface<Scenario> { |
197 public: | 253 public: |
198 AnimatedContentSamplerParameterizedTest() | 254 AnimatedContentSamplerParameterizedTest() |
199 : count_dropped_frames_(0), count_sampled_frames_(0) {} | 255 : count_dropped_frames_(0), count_sampled_frames_(0) {} |
200 virtual ~AnimatedContentSamplerParameterizedTest() {} | 256 virtual ~AnimatedContentSamplerParameterizedTest() {} |
201 | 257 |
| 258 void SetUp() override { |
| 259 AnimatedContentSamplerTest::SetUp(); |
| 260 sampler()->SetTargetSamplingPeriod(GetParam().target_sampling_period); |
| 261 } |
| 262 |
202 protected: | 263 protected: |
203 typedef std::pair<gfx::Rect, base::TimeTicks> Event; | 264 typedef std::pair<gfx::Rect, base::TimeTicks> Event; |
204 | 265 |
205 base::TimeDelta GetMinCapturePeriod() const override { | 266 base::TimeDelta GetMinCapturePeriod() const override { |
206 return GetParam().min_capture_period; | 267 return GetParam().min_capture_period; |
207 } | 268 } |
208 | 269 |
| 270 base::TimeDelta ComputeExpectedSamplingPeriod() const { |
| 271 return AnimatedContentSamplerTest::ComputeSamplingPeriod( |
| 272 GetParam().content_period, |
| 273 GetParam().target_sampling_period, |
| 274 GetParam().min_capture_period); |
| 275 } |
| 276 |
209 // Generate a sequence of events from the compositor pipeline. The event | 277 // Generate a sequence of events from the compositor pipeline. The event |
210 // times will all be at compositor vsync boundaries. | 278 // times will all be at compositor vsync boundaries. |
211 std::vector<Event> GenerateEventSequence(base::TimeTicks begin, | 279 std::vector<Event> GenerateEventSequence(base::TimeTicks begin, |
212 base::TimeTicks end, | 280 base::TimeTicks end, |
213 bool include_content_frame_events, | 281 bool include_content_frame_events, |
214 bool include_random_events) { | 282 bool include_random_events, |
| 283 base::TimeTicks* next_begin_time) { |
215 DCHECK(GetParam().content_period >= GetParam().vsync_interval); | 284 DCHECK(GetParam().content_period >= GetParam().vsync_interval); |
216 base::TimeTicks next_content_time = begin - GetParam().content_period; | 285 base::TimeTicks next_content_time = begin; |
217 std::vector<Event> events; | 286 std::vector<Event> events; |
218 for (base::TimeTicks compositor_time = begin; compositor_time < end; | 287 base::TimeTicks compositor_time; |
| 288 for (compositor_time = begin; compositor_time < end; |
219 compositor_time += GetParam().vsync_interval) { | 289 compositor_time += GetParam().vsync_interval) { |
220 if (include_content_frame_events && next_content_time < compositor_time) { | 290 if (next_content_time <= compositor_time) { |
221 events.push_back(Event(GetContentDamageRect(), compositor_time)); | |
222 next_content_time += GetParam().content_period; | 291 next_content_time += GetParam().content_period; |
223 } else if (include_random_events && GetRandomInRange(0, 1) == 0) { | 292 if (include_content_frame_events) { |
| 293 events.push_back(Event(GetContentDamageRect(), compositor_time)); |
| 294 continue; |
| 295 } |
| 296 } |
| 297 if (include_random_events && GetRandomInRange(0, 1) == 0) { |
224 events.push_back(Event(GetRandomDamageRect(), compositor_time)); | 298 events.push_back(Event(GetRandomDamageRect(), compositor_time)); |
225 } | 299 } |
226 } | 300 } |
227 | 301 |
| 302 if (next_begin_time) { |
| 303 while (compositor_time < next_content_time) |
| 304 compositor_time += GetParam().vsync_interval; |
| 305 *next_begin_time = compositor_time; |
| 306 } |
| 307 |
228 DCHECK(!events.empty()); | 308 DCHECK(!events.empty()); |
229 return events; | 309 return events; |
230 } | 310 } |
231 | 311 |
232 // Feed |events| through the sampler, and detect whether the expected | 312 // Feed |events| through the sampler, and detect whether the expected |
233 // lock-in/out transition occurs. Also, track and measure the frame drop | 313 // lock-in/out transition occurs. Also, track and measure the frame drop |
234 // ratio and check it against the expected drop rate. | 314 // ratio and check it against the expected drop rate. |
235 void RunEventSequence(const std::vector<Event> events, | 315 void RunEventSequence(const std::vector<Event> events, |
236 bool was_detecting_before, | 316 bool was_detecting_before, |
237 bool is_detecting_after, | 317 bool is_detecting_after, |
238 bool simulate_pipeline_back_pressure) { | 318 bool simulate_pipeline_back_pressure, |
| 319 const char* description) { |
| 320 SCOPED_TRACE(::testing::Message() << "Description: " << description); |
| 321 |
239 gfx::Rect first_detected_region; | 322 gfx::Rect first_detected_region; |
240 | 323 |
241 EXPECT_EQ(was_detecting_before, sampler()->HasProposal()); | 324 EXPECT_EQ(was_detecting_before, sampler()->HasProposal()); |
242 bool has_detection_switched = false; | 325 bool has_detection_switched = false; |
| 326 bool has_detection_flip_flopped_once = false; |
243 ResetFrameCounters(); | 327 ResetFrameCounters(); |
244 for (std::vector<Event>::const_iterator i = events.begin(); | 328 for (std::vector<Event>::const_iterator i = events.begin(); |
245 i != events.end(); ++i) { | 329 i != events.end(); ++i) { |
246 sampler()->ConsiderPresentationEvent(i->first, i->second); | 330 sampler()->ConsiderPresentationEvent(i->first, i->second); |
247 | 331 |
248 // Detect when the sampler locks in/out, and that it stays that way for | 332 // Detect when the sampler locks in/out, and that it stays that way for |
249 // all further iterations of this loop. | 333 // all further iterations of this loop. It is permissible for the lock-in |
| 334 // to flip-flop once, but no more than that. |
250 if (!has_detection_switched && | 335 if (!has_detection_switched && |
251 was_detecting_before != sampler()->HasProposal()) { | 336 was_detecting_before != sampler()->HasProposal()) { |
252 has_detection_switched = true; | 337 has_detection_switched = true; |
| 338 } else if (has_detection_switched && |
| 339 is_detecting_after != sampler()->HasProposal()) { |
| 340 ASSERT_FALSE(has_detection_flip_flopped_once); |
| 341 has_detection_flip_flopped_once = true; |
| 342 has_detection_switched = false; |
253 } | 343 } |
254 ASSERT_EQ( | 344 ASSERT_EQ( |
255 has_detection_switched ? is_detecting_after : was_detecting_before, | 345 has_detection_switched ? is_detecting_after : was_detecting_before, |
256 sampler()->HasProposal()); | 346 sampler()->HasProposal()); |
257 | 347 |
258 if (sampler()->HasProposal()) { | 348 if (sampler()->HasProposal()) { |
259 // Make sure the sampler doesn't flip-flop and keep proposing sampling | 349 // Make sure the sampler doesn't flip-flop and keep proposing sampling |
260 // based on locking into different regions. | 350 // based on locking into different regions. |
261 if (first_detected_region.IsEmpty()) { | 351 if (first_detected_region.IsEmpty()) { |
262 first_detected_region = sampler()->detected_region(); | 352 first_detected_region = sampler()->detected_region(); |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
307 } | 397 } |
308 } | 398 } |
309 | 399 |
310 // Confirm the AnimatedContentSampler is not dropping more frames than | 400 // Confirm the AnimatedContentSampler is not dropping more frames than |
311 // expected, given current test parameters. | 401 // expected, given current test parameters. |
312 void ExpectFrameDropRatioIsCorrect() { | 402 void ExpectFrameDropRatioIsCorrect() { |
313 if (count_sampled_frames_ == 0) { | 403 if (count_sampled_frames_ == 0) { |
314 EXPECT_EQ(0, count_dropped_frames_); | 404 EXPECT_EQ(0, count_dropped_frames_); |
315 return; | 405 return; |
316 } | 406 } |
317 const double content_framerate = | 407 const double expected_sampling_ratio = |
318 1000000.0 / GetParam().content_period.InMicroseconds(); | 408 GetParam().content_period.InSecondsF() / |
319 const double capture_framerate = | 409 ComputeExpectedSamplingPeriod().InSecondsF(); |
320 1000000.0 / GetParam().min_capture_period.InMicroseconds(); | 410 const int total_frames = count_dropped_frames_ + count_sampled_frames_; |
321 const double expected_drop_rate = std::max( | 411 EXPECT_NEAR(total_frames * expected_sampling_ratio, |
322 0.0, (content_framerate - capture_framerate) / capture_framerate); | 412 count_sampled_frames_, |
323 const double actual_drop_rate = | 413 1.5); |
324 static_cast<double>(count_dropped_frames_) / count_sampled_frames_; | 414 EXPECT_NEAR(total_frames * (1.0 - expected_sampling_ratio), |
325 EXPECT_NEAR(expected_drop_rate, actual_drop_rate, 0.015); | 415 count_dropped_frames_, |
| 416 1.5); |
326 } | 417 } |
327 | 418 |
328 private: | 419 private: |
329 // These counters only include the frames with the desired content. | 420 // These counters only include the frames with the desired content. |
330 int count_dropped_frames_; | 421 int count_dropped_frames_; |
331 int count_sampled_frames_; | 422 int count_sampled_frames_; |
332 }; | 423 }; |
333 | 424 |
334 // Tests that the implementation locks in/out of frames containing stable | 425 // Tests that the implementation locks in/out of frames containing stable |
335 // animated content, whether or not random events are also simultaneously | 426 // animated content, whether or not random events are also simultaneously |
336 // present. | 427 // present. |
337 TEST_P(AnimatedContentSamplerParameterizedTest, DetectsAnimatedContent) { | 428 TEST_P(AnimatedContentSamplerParameterizedTest, DetectsAnimatedContent) { |
338 // |begin| refers to the start of an event sequence in terms of the | 429 // |begin| refers to the start of an event sequence in terms of the |
339 // Compositor's clock. | 430 // Compositor's clock. |
340 base::TimeTicks begin = InitialTestTimeTicks(); | 431 base::TimeTicks begin = InitialTestTimeTicks(); |
341 | 432 |
342 // Provide random events and expect no lock-in. | 433 // Provide random events and expect no lock-in. |
343 base::TimeTicks end = begin + base::TimeDelta::FromSeconds(5); | 434 RunEventSequence( |
344 RunEventSequence(GenerateEventSequence(begin, end, false, true), | 435 GenerateEventSequence(begin, |
345 false, | 436 begin + base::TimeDelta::FromSeconds(5), |
346 false, | 437 false, |
347 false); | 438 true, |
348 begin = end; | 439 &begin), |
| 440 false, |
| 441 false, |
| 442 false, |
| 443 "Provide random events and expect no lock-in."); |
| 444 if (HasFailure()) |
| 445 return; |
349 | 446 |
350 // Provide content frame events with some random events mixed-in, and expect | 447 // Provide content frame events with some random events mixed-in, and expect |
351 // the sampler to lock-in. | 448 // the sampler to lock-in. |
352 end = begin + base::TimeDelta::FromSeconds(5); | 449 RunEventSequence( |
353 RunEventSequence(GenerateEventSequence(begin, end, true, true), | 450 GenerateEventSequence(begin, |
354 false, | 451 begin + base::TimeDelta::FromSeconds(5), |
355 true, | 452 true, |
356 false); | 453 true, |
357 begin = end; | 454 &begin), |
| 455 false, |
| 456 true, |
| 457 false, |
| 458 "Provide content frame events with some random events mixed-in, and " |
| 459 "expect the sampler to lock-in."); |
| 460 if (HasFailure()) |
| 461 return; |
358 | 462 |
359 // Continue providing content frame events without the random events mixed-in | 463 // Continue providing content frame events without the random events mixed-in |
360 // and expect the lock-in to hold. | 464 // and expect the lock-in to hold. |
361 end = begin + base::TimeDelta::FromSeconds(5); | 465 RunEventSequence( |
362 RunEventSequence(GenerateEventSequence(begin, end, true, false), | 466 GenerateEventSequence(begin, |
363 true, | 467 begin + base::TimeDelta::FromSeconds(5), |
364 true, | 468 true, |
365 false); | 469 false, |
366 begin = end; | 470 &begin), |
| 471 true, |
| 472 true, |
| 473 false, |
| 474 "Continue providing content frame events without the random events " |
| 475 "mixed-in and expect the lock-in to hold."); |
| 476 if (HasFailure()) |
| 477 return; |
367 | 478 |
368 // Continue providing just content frame events and expect the lock-in to | 479 // Continue providing just content frame events and expect the lock-in to |
369 // hold. Also simulate the capture pipeline experiencing back pressure. | 480 // hold. Also simulate the capture pipeline experiencing back pressure. |
370 end = begin + base::TimeDelta::FromSeconds(20); | 481 RunEventSequence( |
371 RunEventSequence(GenerateEventSequence(begin, end, true, false), | 482 GenerateEventSequence(begin, |
372 true, | 483 begin + base::TimeDelta::FromSeconds(20), |
373 true, | 484 true, |
374 true); | 485 false, |
375 begin = end; | 486 &begin), |
| 487 true, |
| 488 true, |
| 489 true, |
| 490 "Continue providing just content frame events and expect the lock-in to " |
| 491 "hold. Also simulate the capture pipeline experiencing back pressure."); |
| 492 if (HasFailure()) |
| 493 return; |
| 494 |
376 | 495 |
377 // Provide a half-second of random events only, and expect the lock-in to be | 496 // Provide a half-second of random events only, and expect the lock-in to be |
378 // broken. | 497 // broken. |
379 end = begin + base::TimeDelta::FromMilliseconds(500); | 498 RunEventSequence( |
380 RunEventSequence(GenerateEventSequence(begin, end, false, true), | 499 GenerateEventSequence(begin, |
381 true, | 500 begin + base::TimeDelta::FromMilliseconds(500), |
382 false, | 501 false, |
383 false); | 502 true, |
384 begin = end; | 503 &begin), |
| 504 true, |
| 505 false, |
| 506 false, |
| 507 "Provide a half-second of random events only, and expect the lock-in to " |
| 508 "be broken."); |
| 509 if (HasFailure()) |
| 510 return; |
385 | 511 |
386 // Now, go back to providing content frame events, and expect the sampler to | 512 // Now, go back to providing content frame events, and expect the sampler to |
387 // lock-in once again. | 513 // lock-in once again. |
388 end = begin + base::TimeDelta::FromSeconds(5); | 514 RunEventSequence( |
389 RunEventSequence(GenerateEventSequence(begin, end, true, false), | 515 GenerateEventSequence(begin, |
390 false, | 516 begin + base::TimeDelta::FromSeconds(5), |
391 true, | 517 true, |
392 false); | 518 false, |
393 begin = end; | 519 &begin), |
| 520 false, |
| 521 true, |
| 522 false, |
| 523 "Now, go back to providing content frame events, and expect the sampler " |
| 524 "to lock-in once again."); |
394 } | 525 } |
395 | 526 |
396 // Tests that AnimatedContentSampler won't lock in to, nor flip-flop between, | 527 // Tests that AnimatedContentSampler won't lock in to, nor flip-flop between, |
397 // two animations of the same pixel change rate. VideoCaptureOracle should | 528 // two animations of the same pixel change rate. VideoCaptureOracle should |
398 // revert to using the SmoothEventSampler for these kinds of situations, as | 529 // revert to using the SmoothEventSampler for these kinds of situations, as |
399 // there is no "right answer" as to which animation to lock into. | 530 // there is no "right answer" as to which animation to lock into. |
400 TEST_P(AnimatedContentSamplerParameterizedTest, | 531 TEST_P(AnimatedContentSamplerParameterizedTest, |
401 DoesNotLockInToTwoCompetingAnimations) { | 532 DoesNotLockInToTwoCompetingAnimations) { |
402 // Don't test when the event stream cannot indicate two separate content | 533 // Don't test when the event stream cannot indicate two separate content |
403 // animations under the current test parameters. | 534 // animations under the current test parameters. |
404 if (GetParam().content_period < 2 * GetParam().vsync_interval) | 535 if (GetParam().content_period < 2 * GetParam().vsync_interval) |
405 return; | 536 return; |
406 | 537 |
407 // Start the first animation and run for a bit, and expect the sampler to | 538 // Start the first animation and run for a bit, and expect the sampler to |
408 // lock-in. | 539 // lock-in. |
409 base::TimeTicks begin = InitialTestTimeTicks(); | 540 base::TimeTicks begin = InitialTestTimeTicks(); |
410 base::TimeTicks end = begin + base::TimeDelta::FromSeconds(5); | 541 RunEventSequence( |
411 RunEventSequence(GenerateEventSequence(begin, end, true, false), | 542 GenerateEventSequence(begin, |
412 false, | 543 begin + base::TimeDelta::FromSeconds(5), |
413 true, | 544 true, |
414 false); | 545 false, |
415 begin = end; | 546 &begin), |
| 547 false, |
| 548 true, |
| 549 false, |
| 550 "Start the first animation and run for a bit, and expect the sampler to " |
| 551 "lock-in."); |
| 552 if (HasFailure()) |
| 553 return; |
416 | 554 |
417 // Now, keep the first animation and blend in an second animation of the same | 555 // Now, keep the first animation and blend in a second animation of the same |
418 // size and frame rate, but at a different position. This will should cause | 556 // size and frame rate, but at a different position. This will should cause |
419 // the sampler to enter an "undetected" state since it's unclear which | 557 // the sampler to enter an "undetected" state since it's unclear which |
420 // animation should be locked into. | 558 // animation should be locked into. |
421 end = begin + base::TimeDelta::FromSeconds(20); | |
422 std::vector<Event> first_animation_events = | 559 std::vector<Event> first_animation_events = |
423 GenerateEventSequence(begin, end, true, false); | 560 GenerateEventSequence(begin, |
| 561 begin + base::TimeDelta::FromSeconds(20), |
| 562 true, |
| 563 false, |
| 564 &begin); |
424 gfx::Rect second_animation_rect( | 565 gfx::Rect second_animation_rect( |
425 gfx::Point(0, GetContentDamageRect().height()), | 566 gfx::Point(0, GetContentDamageRect().height()), |
426 GetContentDamageRect().size()); | 567 GetContentDamageRect().size()); |
427 std::vector<Event> both_animations_events; | 568 std::vector<Event> both_animations_events; |
428 base::TimeDelta second_animation_offset = GetParam().vsync_interval; | 569 base::TimeDelta second_animation_offset = GetParam().vsync_interval; |
429 for (std::vector<Event>::const_iterator i = first_animation_events.begin(); | 570 for (std::vector<Event>::const_iterator i = first_animation_events.begin(); |
430 i != first_animation_events.end(); ++i) { | 571 i != first_animation_events.end(); ++i) { |
431 both_animations_events.push_back(*i); | 572 both_animations_events.push_back(*i); |
432 both_animations_events.push_back( | 573 both_animations_events.push_back( |
433 Event(second_animation_rect, i->second + second_animation_offset)); | 574 Event(second_animation_rect, i->second + second_animation_offset)); |
434 } | 575 } |
435 RunEventSequence(both_animations_events, true, false, false); | 576 RunEventSequence( |
436 begin = end; | 577 both_animations_events, true, false, false, |
| 578 "Now, blend-in a second animation of the same size and frame rate, but " |
| 579 "at a different position."); |
| 580 if (HasFailure()) |
| 581 return; |
437 | 582 |
438 // Now, run just the first animation, and expect the sampler to lock-in once | 583 // Now, run just the first animation, and expect the sampler to lock-in once |
439 // again. | 584 // again. |
440 end = begin + base::TimeDelta::FromSeconds(5); | 585 RunEventSequence( |
441 RunEventSequence(GenerateEventSequence(begin, end, true, false), | 586 GenerateEventSequence(begin, |
442 false, | 587 begin + base::TimeDelta::FromSeconds(5), |
443 true, | 588 true, |
444 false); | 589 false, |
445 begin = end; | 590 &begin), |
| 591 false, |
| 592 true, |
| 593 false, |
| 594 "Now, run just the first animation, and expect the sampler to lock-in " |
| 595 "once again."); |
| 596 if (HasFailure()) |
| 597 return; |
446 | 598 |
447 // Now, blend in the second animation again, but it has half the frame rate of | 599 // Now, blend in the second animation again, but it has half the frame rate of |
448 // the first animation and damage Rects with twice the area. This will should | 600 // the first animation and damage Rects with twice the area. This will should |
449 // cause the sampler to enter an "undetected" state again. This tests that | 601 // cause the sampler to enter an "undetected" state again. This tests that |
450 // pixel-weighting is being accounted for in the sampler's logic. | 602 // pixel-weighting is being accounted for in the sampler's logic. |
451 end = begin + base::TimeDelta::FromSeconds(20); | 603 first_animation_events = |
452 first_animation_events = GenerateEventSequence(begin, end, true, false); | 604 GenerateEventSequence(begin, |
| 605 begin + base::TimeDelta::FromSeconds(20), |
| 606 true, |
| 607 false, |
| 608 &begin); |
453 second_animation_rect.set_width(second_animation_rect.width() * 2); | 609 second_animation_rect.set_width(second_animation_rect.width() * 2); |
454 both_animations_events.clear(); | 610 both_animations_events.clear(); |
455 bool include_second_animation_frame = true; | 611 bool include_second_animation_frame = true; |
456 for (std::vector<Event>::const_iterator i = first_animation_events.begin(); | 612 for (std::vector<Event>::const_iterator i = first_animation_events.begin(); |
457 i != first_animation_events.end(); ++i) { | 613 i != first_animation_events.end(); ++i) { |
458 both_animations_events.push_back(*i); | 614 both_animations_events.push_back(*i); |
459 if (include_second_animation_frame) { | 615 if (include_second_animation_frame) { |
460 both_animations_events.push_back( | 616 both_animations_events.push_back( |
461 Event(second_animation_rect, i->second + second_animation_offset)); | 617 Event(second_animation_rect, i->second + second_animation_offset)); |
462 } | 618 } |
463 include_second_animation_frame = !include_second_animation_frame; | 619 include_second_animation_frame = !include_second_animation_frame; |
464 } | 620 } |
465 RunEventSequence(both_animations_events, true, false, false); | 621 RunEventSequence( |
466 begin = end; | 622 both_animations_events, true, false, false, |
| 623 "Now, blend in the second animation again, but it has half the frame " |
| 624 "rate of the first animation and damage Rects with twice the area."); |
467 } | 625 } |
468 | 626 |
469 // Tests that the frame timestamps are smooth; meaning, that when run through a | 627 // Tests that the frame timestamps are smooth; meaning, that when run through a |
470 // simulated compositor, each frame is held displayed for the right number of | 628 // simulated compositor, each frame is held displayed for the right number of |
471 // v-sync intervals. | 629 // v-sync intervals. |
472 TEST_P(AnimatedContentSamplerParameterizedTest, FrameTimestampsAreSmooth) { | 630 TEST_P(AnimatedContentSamplerParameterizedTest, FrameTimestampsAreSmooth) { |
473 // Generate 30 seconds of animated content events, run the events through | 631 // Generate 30 seconds of animated content events, run the events through |
474 // AnimatedContentSampler, and record all frame timestamps being proposed | 632 // AnimatedContentSampler, and record all frame timestamps being proposed |
475 // once lock-in is continuous. | 633 // once lock-in is continuous. |
476 base::TimeTicks begin = InitialTestTimeTicks(); | 634 const base::TimeTicks begin = InitialTestTimeTicks(); |
477 std::vector<Event> events = GenerateEventSequence( | 635 std::vector<Event> events = GenerateEventSequence( |
478 begin, | 636 begin, |
479 begin + base::TimeDelta::FromSeconds(20), | 637 begin + base::TimeDelta::FromSeconds(20), |
480 true, | 638 true, |
481 false); | 639 false, |
| 640 nullptr); |
482 typedef std::vector<base::TimeTicks> Timestamps; | 641 typedef std::vector<base::TimeTicks> Timestamps; |
483 Timestamps frame_timestamps; | 642 Timestamps frame_timestamps; |
484 for (std::vector<Event>::const_iterator i = events.begin(); i != events.end(); | 643 for (std::vector<Event>::const_iterator i = events.begin(); i != events.end(); |
485 ++i) { | 644 ++i) { |
486 sampler()->ConsiderPresentationEvent(i->first, i->second); | 645 sampler()->ConsiderPresentationEvent(i->first, i->second); |
487 if (sampler()->HasProposal()) { | 646 if (sampler()->HasProposal()) { |
488 if (sampler()->ShouldSample()) { | 647 if (sampler()->ShouldSample()) { |
489 frame_timestamps.push_back(sampler()->frame_timestamp()); | 648 frame_timestamps.push_back(sampler()->frame_timestamp()); |
490 sampler()->RecordSample(sampler()->frame_timestamp()); | 649 sampler()->RecordSample(sampler()->frame_timestamp()); |
491 } | 650 } |
492 } else { | 651 } else { |
493 frame_timestamps.clear(); // Reset until continuous lock-in. | 652 frame_timestamps.clear(); // Reset until continuous lock-in. |
494 } | 653 } |
495 } | 654 } |
496 ASSERT_LE(2u, frame_timestamps.size()); | 655 ASSERT_LE(2u, frame_timestamps.size()); |
497 | 656 |
498 // Iterate through the |frame_timestamps|, building a histogram counting the | 657 // Iterate through the |frame_timestamps|, building a histogram counting the |
499 // number of times each frame was displayed k times. For example, 10 frames | 658 // number of times each frame was displayed k times. For example, 10 frames |
500 // of 30 Hz content on a 60 Hz v-sync interval should result in | 659 // of 30 Hz content on a 60 Hz v-sync interval should result in |
501 // display_counts[2] == 10. Quit early if any one frame was obviously | 660 // display_counts[2] == 10. Quit early if any one frame was obviously |
502 // repeated too many times. | 661 // repeated too many times. |
503 const int64 max_expected_repeats_per_frame = 1 + | 662 const int64 max_expected_repeats_per_frame = 1 + |
504 std::max(GetParam().min_capture_period, GetParam().content_period) / | 663 ComputeExpectedSamplingPeriod() / GetParam().vsync_interval; |
505 GetParam().vsync_interval; | |
506 std::vector<size_t> display_counts(max_expected_repeats_per_frame + 1, 0); | 664 std::vector<size_t> display_counts(max_expected_repeats_per_frame + 1, 0); |
507 base::TimeTicks last_present_time = frame_timestamps.front(); | 665 base::TimeTicks last_present_time = frame_timestamps.front(); |
508 for (Timestamps::const_iterator i = frame_timestamps.begin() + 1; | 666 for (Timestamps::const_iterator i = frame_timestamps.begin() + 1; |
509 i != frame_timestamps.end(); ++i) { | 667 i != frame_timestamps.end(); ++i) { |
510 const size_t num_vsync_intervals = static_cast<size_t>( | 668 const size_t num_vsync_intervals = static_cast<size_t>( |
511 (*i - last_present_time) / GetParam().vsync_interval); | 669 (*i - last_present_time) / GetParam().vsync_interval); |
512 ASSERT_LT(0u, num_vsync_intervals); | 670 ASSERT_LT(0u, num_vsync_intervals); |
513 ASSERT_GT(display_counts.size(), num_vsync_intervals); // Quit early. | 671 ASSERT_GT(display_counts.size(), num_vsync_intervals); // Quit early. |
514 ++display_counts[num_vsync_intervals]; | 672 ++display_counts[num_vsync_intervals]; |
515 last_present_time += num_vsync_intervals * GetParam().vsync_interval; | 673 last_present_time += num_vsync_intervals * GetParam().vsync_interval; |
(...skipping 18 matching lines...) Expand all Loading... |
534 } | 692 } |
535 } | 693 } |
536 size_t stray_count_remaining = | 694 size_t stray_count_remaining = |
537 (frame_timestamps.size() - 1) - (highest_count + second_highest_count); | 695 (frame_timestamps.size() - 1) - (highest_count + second_highest_count); |
538 // Expect no more than 0.75% of frames fall outside the two main buckets. | 696 // Expect no more than 0.75% of frames fall outside the two main buckets. |
539 EXPECT_GT(frame_timestamps.size() * 75 / 10000, stray_count_remaining); | 697 EXPECT_GT(frame_timestamps.size() * 75 / 10000, stray_count_remaining); |
540 for (size_t repeats = 1; repeats < display_counts.size() - 1; ++repeats) { | 698 for (size_t repeats = 1; repeats < display_counts.size() - 1; ++repeats) { |
541 if (display_counts[repeats] == highest_count) { | 699 if (display_counts[repeats] == highest_count) { |
542 EXPECT_EQ(second_highest_count, display_counts[repeats + 1]); | 700 EXPECT_EQ(second_highest_count, display_counts[repeats + 1]); |
543 ++repeats; | 701 ++repeats; |
544 } else if (display_counts[repeats] == second_highest_count) { | 702 } else if (second_highest_count > 0 && |
| 703 display_counts[repeats] == second_highest_count) { |
545 EXPECT_EQ(highest_count, display_counts[repeats + 1]); | 704 EXPECT_EQ(highest_count, display_counts[repeats + 1]); |
546 ++repeats; | 705 ++repeats; |
547 } else { | 706 } else { |
548 EXPECT_GE(stray_count_remaining, display_counts[repeats]); | 707 EXPECT_GE(stray_count_remaining, display_counts[repeats]); |
549 stray_count_remaining -= display_counts[repeats]; | 708 stray_count_remaining -= display_counts[repeats]; |
550 } | 709 } |
551 } | 710 } |
552 } | 711 } |
553 | 712 |
554 // Tests that frame timestamps are "lightly pushed" back towards the original | 713 // Tests that frame timestamps are "lightly pushed" back towards the original |
555 // presentation event times, which tells us the AnimatedContentSampler can | 714 // presentation event times, which tells us the AnimatedContentSampler can |
556 // account for sources of timestamp drift and correct the drift. | 715 // account for sources of timestamp drift and correct the drift. |
557 TEST_P(AnimatedContentSamplerParameterizedTest, | 716 TEST_P(AnimatedContentSamplerParameterizedTest, |
558 FrameTimestampsConvergeTowardsEventTimes) { | 717 FrameTimestampsConvergeTowardsEventTimes) { |
559 const int max_drift_increment_millis = 3; | 718 const int max_drift_increment_millis = 3; |
560 | 719 |
561 // Generate a full minute of events. | 720 // Generate a full minute of events. |
562 const base::TimeTicks begin = InitialTestTimeTicks(); | 721 const base::TimeTicks begin = InitialTestTimeTicks(); |
563 const base::TimeTicks end = begin + base::TimeDelta::FromMinutes(1); | 722 std::vector<Event> events = |
564 std::vector<Event> events = GenerateEventSequence(begin, end, true, false); | 723 GenerateEventSequence(begin, |
| 724 begin + base::TimeDelta::FromMinutes(1), |
| 725 true, |
| 726 false, |
| 727 nullptr); |
565 | 728 |
566 // Modify the event sequence so that 1-3 ms of additional drift is suddenly | 729 // Modify the event sequence so that 1-3 ms of additional drift is suddenly |
567 // present every 100 events. This is meant to simulate that, external to | 730 // present every 100 events. This is meant to simulate that, external to |
568 // AnimatedContentSampler, the video hardware vsync timebase is being | 731 // AnimatedContentSampler, the video hardware vsync timebase is being |
569 // refreshed and is showing severe drift from the system clock. | 732 // refreshed and is showing severe drift from the system clock. |
570 base::TimeDelta accumulated_drift; | 733 base::TimeDelta accumulated_drift; |
571 for (size_t i = 1; i < events.size(); ++i) { | 734 for (size_t i = 1; i < events.size(); ++i) { |
572 if (i % 100 == 0) { | 735 if (i % 100 == 0) { |
573 accumulated_drift += base::TimeDelta::FromMilliseconds( | 736 accumulated_drift += base::TimeDelta::FromMilliseconds( |
574 GetRandomInRange(1, max_drift_increment_millis + 1)); | 737 GetRandomInRange(1, max_drift_increment_millis + 1)); |
(...skipping 21 matching lines...) Expand all Loading... |
596 total_error.InMicroseconds(), | 759 total_error.InMicroseconds(), |
597 max_acceptable_error.InMicroseconds()); | 760 max_acceptable_error.InMicroseconds()); |
598 } | 761 } |
599 | 762 |
600 INSTANTIATE_TEST_CASE_P( | 763 INSTANTIATE_TEST_CASE_P( |
601 , | 764 , |
602 AnimatedContentSamplerParameterizedTest, | 765 AnimatedContentSamplerParameterizedTest, |
603 ::testing::Values( | 766 ::testing::Values( |
604 // Typical frame rate content: Compositor runs at 60 Hz, capture at 30 | 767 // Typical frame rate content: Compositor runs at 60 Hz, capture at 30 |
605 // Hz, and content video animates at 30, 25, or 24 Hz. | 768 // Hz, and content video animates at 30, 25, or 24 Hz. |
606 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(30)), | 769 Scenario(60, 30, 30), |
607 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(25)), | 770 Scenario(60, 30, 25), |
608 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(24)), | 771 Scenario(60, 30, 24), |
609 | 772 |
610 // High frame rate content that leverages the Compositor's | 773 // High frame rate content that leverages the Compositor's |
611 // capabilities, but capture is still at 30 Hz. | 774 // capabilities, but capture is still at 30 Hz. |
612 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(60)), | 775 Scenario(60, 30, 60), |
613 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(50)), | 776 Scenario(60, 30, 50), |
614 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(48)), | 777 Scenario(60, 30, 48), |
615 | 778 |
616 // High frame rate content that leverages the Compositor's | 779 // High frame rate content that leverages the Compositor's |
617 // capabilities, and capture is also a buttery 60 Hz. | 780 // capabilities, and capture is also a buttery 60 Hz. |
618 Scenario(FpsAsPeriod(60), FpsAsPeriod(60), FpsAsPeriod(60)), | 781 Scenario(60, 60, 60), |
619 Scenario(FpsAsPeriod(60), FpsAsPeriod(60), FpsAsPeriod(50)), | 782 Scenario(60, 60, 50), |
620 Scenario(FpsAsPeriod(60), FpsAsPeriod(60), FpsAsPeriod(48)), | 783 Scenario(60, 60, 48), |
| 784 |
| 785 // High frame rate content that leverages the Compositor's |
| 786 // capabilities, but the client has disabled HFR sampling. |
| 787 Scenario(60, 60, 60, 30), |
| 788 Scenario(60, 60, 50, 30), |
| 789 Scenario(60, 60, 48, 30), |
621 | 790 |
622 // On some platforms, the Compositor runs at 50 Hz. | 791 // On some platforms, the Compositor runs at 50 Hz. |
623 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(30)), | 792 Scenario(50, 30, 30), |
624 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(25)), | 793 Scenario(50, 30, 25), |
625 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(24)), | 794 Scenario(50, 30, 24), |
626 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(50)), | 795 Scenario(50, 30, 50), |
627 Scenario(FpsAsPeriod(50), FpsAsPeriod(30), FpsAsPeriod(48)), | 796 Scenario(50, 30, 48), |
628 | 797 |
629 // Stable, but non-standard content frame rates. | 798 // Stable, but non-standard content frame rates. |
630 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(16)), | 799 Scenario(60, 30, 16), |
631 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(20)), | 800 Scenario(60, 30, 20), |
632 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(23)), | 801 Scenario(60, 30, 23), |
633 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(26)), | 802 Scenario(60, 30, 26), |
634 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(27)), | 803 Scenario(60, 30, 27), |
635 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(28)), | 804 Scenario(60, 30, 28), |
636 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(29)), | 805 Scenario(60, 30, 29), |
637 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(31)), | 806 Scenario(60, 30, 31), |
638 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(32)), | 807 Scenario(60, 30, 32), |
639 Scenario(FpsAsPeriod(60), FpsAsPeriod(30), FpsAsPeriod(33)))); | 808 Scenario(60, 30, 33))); |
640 | 809 |
641 } // namespace content | 810 } // namespace content |
OLD | NEW |