babylon.performanceMonitor.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. namespace BABYLON {
  2. /**
  3. * Performance monitor tracks rolling average frame-time and frame-time variance over a user defined sliding-window
  4. */
  5. export class PerformanceMonitor {
  6. private _enabled: boolean = true;
  7. private _rollingFrameTime: RollingAverage;
  8. private _lastFrameTimeMs: Nullable<number>;
  9. /**
  10. * constructor
  11. * @param frameSampleSize The number of samples required to saturate the sliding window
  12. */
  13. constructor(frameSampleSize: number = 30) {
  14. this._rollingFrameTime = new RollingAverage(frameSampleSize);
  15. }
  16. /**
  17. * Samples current frame
  18. * @param timeMs A timestamp in milliseconds of the current frame to compare with other frames
  19. */
  20. public sampleFrame(timeMs: number = Tools.Now) {
  21. if (!this._enabled) return;
  22. if (this._lastFrameTimeMs != null) {
  23. let dt = timeMs - this._lastFrameTimeMs;
  24. this._rollingFrameTime.add(dt);
  25. }
  26. this._lastFrameTimeMs = timeMs;
  27. }
  28. /**
  29. * Returns the average frame time in milliseconds over the sliding window (or the subset of frames sampled so far)
  30. * @return Average frame time in milliseconds
  31. */
  32. public get averageFrameTime(): number {
  33. return this._rollingFrameTime.average;
  34. }
  35. /**
  36. * Returns the variance frame time in milliseconds over the sliding window (or the subset of frames sampled so far)
  37. * @return Frame time variance in milliseconds squared
  38. */
  39. public get averageFrameTimeVariance(): number {
  40. return this._rollingFrameTime.variance;
  41. }
  42. /**
  43. * Returns the frame time of the most recent frame
  44. * @return Frame time in milliseconds
  45. */
  46. public get instantaneousFrameTime(): number {
  47. return this._rollingFrameTime.history(0);
  48. }
  49. /**
  50. * Returns the average framerate in frames per second over the sliding window (or the subset of frames sampled so far)
  51. * @return Framerate in frames per second
  52. */
  53. public get averageFPS(): number {
  54. return 1000.0 / this._rollingFrameTime.average;
  55. }
  56. /**
  57. * Returns the average framerate in frames per second using the most recent frame time
  58. * @return Framerate in frames per second
  59. */
  60. public get instantaneousFPS(): number {
  61. let history = this._rollingFrameTime.history(0);
  62. if (history === 0) {
  63. return 0;
  64. }
  65. return 1000.0 / history;
  66. }
  67. /**
  68. * Returns true if enough samples have been taken to completely fill the sliding window
  69. * @return true if saturated
  70. */
  71. public get isSaturated(): boolean {
  72. return this._rollingFrameTime.isSaturated();
  73. }
  74. /**
  75. * Enables contributions to the sliding window sample set
  76. */
  77. public enable() {
  78. this._enabled = true;
  79. }
  80. /**
  81. * Disables contributions to the sliding window sample set
  82. * Samples will not be interpolated over the disabled period
  83. */
  84. public disable() {
  85. this._enabled = false;
  86. //clear last sample to avoid interpolating over the disabled period when next enabled
  87. this._lastFrameTimeMs = null;
  88. }
  89. /**
  90. * Returns true if sampling is enabled
  91. * @return true if enabled
  92. */
  93. public get isEnabled(): boolean {
  94. return this._enabled;
  95. }
  96. /**
  97. * Resets performance monitor
  98. */
  99. public reset() {
  100. //clear last sample to avoid interpolating over the disabled period when next enabled
  101. this._lastFrameTimeMs = null;
  102. //wipe record
  103. this._rollingFrameTime.reset();
  104. }
  105. }
  106. /**
  107. * RollingAverage
  108. *
  109. * Utility to efficiently compute the rolling average and variance over a sliding window of samples
  110. */
  111. export class RollingAverage {
  112. /**
  113. * Current average
  114. */
  115. public average: number;
  116. /**
  117. * Current variance
  118. */
  119. public variance: number;
  120. protected _samples: Array<number>;
  121. protected _sampleCount: number;
  122. protected _pos: number;
  123. protected _m2: number;//sum of squares of differences from the (current) mean
  124. /**
  125. * constructor
  126. * @param length The number of samples required to saturate the sliding window
  127. */
  128. constructor(length: number) {
  129. this._samples = new Array<number>(length);
  130. this.reset();
  131. }
  132. /**
  133. * Adds a sample to the sample set
  134. * @param v The sample value
  135. */
  136. public add(v: number) {
  137. //http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
  138. let delta: number;
  139. //we need to check if we've already wrapped round
  140. if (this.isSaturated()) {
  141. //remove bottom of stack from mean
  142. let bottomValue = this._samples[this._pos];
  143. delta = bottomValue - this.average;
  144. this.average -= delta / (this._sampleCount - 1);
  145. this._m2 -= delta * (bottomValue - this.average);
  146. } else {
  147. this._sampleCount++;
  148. }
  149. //add new value to mean
  150. delta = v - this.average;
  151. this.average += delta / (this._sampleCount);
  152. this._m2 += delta * (v - this.average);
  153. //set the new variance
  154. this.variance = this._m2 / (this._sampleCount - 1);
  155. this._samples[this._pos] = v;
  156. this._pos++;
  157. this._pos %= this._samples.length;//positive wrap around
  158. }
  159. /**
  160. * Returns previously added values or null if outside of history or outside the sliding window domain
  161. * @param i Index in history. For example, pass 0 for the most recent value and 1 for the value before that
  162. * @return Value previously recorded with add() or null if outside of range
  163. */
  164. public history(i: number): number {
  165. if ((i >= this._sampleCount) || (i >= this._samples.length)) {
  166. return 0;
  167. }
  168. let i0 = this._wrapPosition(this._pos - 1.0);
  169. return this._samples[this._wrapPosition(i0 - i)];
  170. }
  171. /**
  172. * Returns true if enough samples have been taken to completely fill the sliding window
  173. * @return true if sample-set saturated
  174. */
  175. public isSaturated(): boolean {
  176. return this._sampleCount >= this._samples.length;
  177. }
  178. /**
  179. * Resets the rolling average (equivalent to 0 samples taken so far)
  180. */
  181. public reset() {
  182. this.average = 0;
  183. this.variance = 0;
  184. this._sampleCount = 0;
  185. this._pos = 0;
  186. this._m2 = 0;
  187. }
  188. /**
  189. * Wraps a value around the sample range boundaries
  190. * @param i Position in sample range, for example if the sample length is 5, and i is -3, then 2 will be returned.
  191. * @return Wrapped position in sample range
  192. */
  193. protected _wrapPosition(i: number): number {
  194. let max = this._samples.length;
  195. return ((i % max) + max) % max;
  196. }
  197. }
  198. }