1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5'use strict';
6
7/**
8 * @fileoverview This file provides the RenderingStats object, used
9 * to characterize rendering smoothness.
10 */
11(function() {
12  var getTimeMs = (function() {
13    if (window.performance)
14      return (performance.now       ||
15              performance.mozNow    ||
16              performance.msNow     ||
17              performance.oNow      ||
18              performance.webkitNow).bind(window.performance);
19    else
20      return function() { return new Date().getTime(); };
21  })();
22
23  var requestAnimationFrame = (function() {
24    return window.requestAnimationFrame       ||
25           window.webkitRequestAnimationFrame ||
26           window.mozRequestAnimationFrame    ||
27           window.oRequestAnimationFrame      ||
28           window.msRequestAnimationFrame     ||
29           function(callback) {
30             window.setTimeout(callback, 1000 / 60);
31           };
32  })().bind(window);
33
34  /**
35   * Tracks rendering performance using the gpuBenchmarking.renderingStats API.
36   * @constructor
37   */
38  function GpuBenchmarkingRenderingStats() {
39  }
40
41  GpuBenchmarkingRenderingStats.prototype.start = function() {
42    this.startTime_ = getTimeMs();
43    this.initialStats_ = this.getRenderingStats_();
44  }
45
46  GpuBenchmarkingRenderingStats.prototype.stop = function() {
47    this.stopTime_ = getTimeMs();
48    this.finalStats_ = this.getRenderingStats_();
49  }
50
51  GpuBenchmarkingRenderingStats.prototype.getStartValues = function() {
52    if (!this.initialStats_)
53      throw new Error('Start not called.');
54
55    if (!this.finalStats_)
56      throw new Error('Stop was not called.');
57
58    return this.initialStats_;
59  }
60
61  GpuBenchmarkingRenderingStats.prototype.getEndValues = function() {
62    if (!this.initialStats_)
63      throw new Error('Start not called.');
64
65    if (!this.finalStats_)
66      throw new Error('Stop was not called.');
67
68    return this.finalStats_;
69  }
70
71  GpuBenchmarkingRenderingStats.prototype.getDeltas = function() {
72    if (!this.initialStats_)
73      throw new Error('Start not called.');
74
75    if (!this.finalStats_)
76      throw new Error('Stop was not called.');
77
78    var stats = {}
79    for (var key in this.finalStats_)
80      stats[key] = this.finalStats_[key] - this.initialStats_[key];
81    return stats;
82  };
83
84  GpuBenchmarkingRenderingStats.prototype.getRenderingStats_ = function() {
85    var stats = chrome.gpuBenchmarking.renderingStats();
86    stats.totalTimeInSeconds = getTimeMs() / 1000;
87    return stats;
88  };
89
90  /**
91   * Tracks rendering performance using requestAnimationFrame.
92   * @constructor
93   */
94  function RafRenderingStats() {
95    this.recording_ = false;
96    this.frameTimes_ = [];
97  }
98
99  RafRenderingStats.prototype.start = function() {
100    if (this.recording_)
101      throw new Error('Already started.');
102    this.recording_ = true;
103    requestAnimationFrame(this.recordFrameTime_.bind(this));
104  }
105
106  RafRenderingStats.prototype.stop = function() {
107    this.recording_ = false;
108  }
109
110  RafRenderingStats.prototype.getStartValues = function() {
111    var results = {};
112    results.numAnimationFrames = 0;
113    results.numFramesSentToScreen = 0;
114    results.droppedFrameCount = 0;
115    return results;
116  }
117
118  RafRenderingStats.prototype.getEndValues = function() {
119    var results = {};
120    results.numAnimationFrames = this.frameTimes_.length - 1;
121    results.numFramesSentToScreen = results.numAnimationFrames;
122    results.droppedFrameCount = this.getDroppedFrameCount_(this.frameTimes_);
123    return results;
124  }
125
126  RafRenderingStats.prototype.getDeltas = function() {
127    var endValues = this.getEndValues();
128    endValues.totalTimeInSeconds = (
129        this.frameTimes_[this.frameTimes_.length - 1] -
130        this.frameTimes_[0]) / 1000;
131    return endValues;
132  };
133
134  RafRenderingStats.prototype.recordFrameTime_ = function(timestamp) {
135    if (!this.recording_)
136      return;
137
138    this.frameTimes_.push(timestamp);
139    requestAnimationFrame(this.recordFrameTime_.bind(this));
140  };
141
142  RafRenderingStats.prototype.getDroppedFrameCount_ = function(frameTimes) {
143    var droppedFrameCount = 0;
144    for (var i = 1; i < frameTimes.length; i++) {
145      var frameTime = frameTimes[i] - frameTimes[i-1];
146      if (frameTime > 1000 / 55)
147        droppedFrameCount++;
148    }
149    return droppedFrameCount;
150  };
151
152  function RenderingStats() {
153    if (window.chrome && chrome.gpuBenchmarking &&
154        chrome.gpuBenchmarking.renderingStats) {
155      return new GpuBenchmarkingRenderingStats();
156    }
157    return new RafRenderingStats();
158  }
159
160  window.__RenderingStats = RenderingStats;
161})();
162