1 /** 2 * The Render Engine 3 * 4 * An extension to the engine for metrics processing and display. 5 * 6 * @author: Brett Fattori (brettf@renderengine.com) 7 * 8 * @author: $Author: bfattori $ 9 * @version: $Revision: 1555 $ 10 * 11 * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com) 12 * 13 * Permission is hereby granted, free of charge, to any person obtaining a copy 14 * of this software and associated documentation files (the "Software"), to deal 15 * in the Software without restriction, including without limitation the rights 16 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 * copies of the Software, and to permit persons to whom the Software is 18 * furnished to do so, subject to the following conditions: 19 * 20 * The above copyright notice and this permission notice shall be included in 21 * all copies or substantial portions of the Software. 22 * 23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 * THE SOFTWARE. 30 * 31 */ 32 33 //==================================================================================================== 34 //==================================================================================================== 35 // METRICS MANAGEMENT 36 //==================================================================================================== 37 //==================================================================================================== 38 /** 39 * @class A static class to be used to measure metrics of engine and game performance. A 40 * visual profiler is provided which graphs runtime values of the engine, such as 41 * load and visible objects. Additionally a metrics window is provided to show 42 * sampled metric data from parts of the engine, as well as user-defined metrics. 43 * @static 44 */ 45 R.debug.Metrics = Base.extend(/** @scope R.debug.Metrics.prototype */{ 46 constructor:null, 47 48 /* 49 * Metrics tracking/display 50 */ 51 metrics:{}, // Tracked metrics 52 metricDisplay:null, // The metric display object 53 profileDisplay:null, // The profile display object 54 metricSampleRate:10, // Frames between samples 55 lastMetricSample:10, // Last sample frame 56 showMetricsWindow:false, // Metrics display flag 57 showMetricsProfile:false, // Metrics profile graph display flag 58 vObj:0, // Visible objects 59 droppedFrames:0, // Non-rendered frames/frames dropped 60 profilePos:0, 61 profiles:{}, 62 63 64 /** 65 * Toggle the display of the metrics window. Any metrics 66 * that are being tracked will be reported in this window. 67 * @memberOf R.debug.Metrics 68 */ 69 toggleMetrics:function () { 70 R.debug.Metrics.showMetricsWindow = !R.debug.Metrics.showMetricsWindow; 71 }, 72 73 /** 74 * Show the metrics window 75 * @memberOf R.debug.Metrics 76 */ 77 showMetrics:function () { 78 R.debug.Metrics.showMetricsWindow = true; 79 }, 80 81 /** 82 * Show a graph of the engine profile 83 * @memberOf R.debug.Metrics 84 */ 85 showProfile:function () { 86 R.debug.Metrics.showMetricsProfile = true; 87 }, 88 89 /** 90 * Hide the metrics window 91 * @memberOf R.debug.Metrics 92 */ 93 hideMetrics:function () { 94 R.debug.Metrics.showMetricsWindow = false; 95 }, 96 97 manMetrics:function () { 98 if ($("div.metric-button.minimize").length > 0) { 99 $("div.metric-button.minimize").removeClass("minimize").addClass("maximize").attr("title", "maximize"); 100 $("div.metrics").css("height", 17); 101 $("div.metrics .items").hide(); 102 } else { 103 $("div.metric-button.maximize").removeClass("maximize").addClass("minimize").attr("title", "minimize"); 104 $("div.metrics .items").show(); 105 $("div.metrics").css("height", "auto"); 106 } 107 }, 108 109 /** 110 * Creates a button for the metrics window 111 * @private 112 */ 113 metricButton:function (cssClass, fn) { 114 return $("<div class='metric-button " + cssClass + "' title='" + cssClass + "'><!-- --></div>").click(fn); 115 }, 116 117 /** 118 * Render the metrics window 119 * @private 120 */ 121 render:function () { 122 123 if (R.debug.Metrics.showMetricsWindow && !R.debug.Metrics.metricDisplay) { 124 R.debug.Metrics.metricDisplay = $("<div/>").addClass("metrics"); 125 R.debug.Metrics.metricDisplay.append(R.debug.Metrics.metricButton("run", function () { 126 R.Engine.run(); 127 })); 128 R.debug.Metrics.metricDisplay.append(R.debug.Metrics.metricButton("step", function () { 129 R.Engine.step(); 130 })); 131 R.debug.Metrics.metricDisplay.append(R.debug.Metrics.metricButton("pause", function () { 132 R.Engine.pause(); 133 })); 134 R.debug.Metrics.metricDisplay.append(R.debug.Metrics.metricButton("shutdown", function () { 135 R.Engine.shutdown(); 136 })); 137 138 R.debug.Metrics.metricDisplay.append(R.debug.Metrics.metricButton("close", function () { 139 R.debug.Metrics.hideMetrics(); 140 })); 141 R.debug.Metrics.metricDisplay.append(R.debug.Metrics.metricButton("minimize", function () { 142 R.debug.Metrics.manMetrics(); 143 })); 144 145 R.debug.Metrics.metricDisplay.append($("<div class='items'/>")); 146 R.debug.Metrics.metricDisplay.appendTo($("body")); 147 } 148 149 if ((this.showMetricsWindow || this.showMetricsProfile) && this.lastMetricSample-- == 0) { 150 // Basic engine metrics 151 R.debug.Metrics.add("FPS", R.Engine.getFPS(), false, "#"); 152 R.debug.Metrics.add("aFPS", R.Engine.getActualFPS(), true, "#"); 153 R.debug.Metrics.add("availTime", R.Engine.fpsClock, false, "#ms"); 154 R.debug.Metrics.add("frames", R.Engine.totalFrames, false, "#"); 155 R.debug.Metrics.add("frameGenTime", R.Engine.frameTime, true, "#ms"); 156 R.debug.Metrics.add("engineLoad", Math.floor(R.Engine.getEngineLoad() * 100), true, "#%"); 157 R.debug.Metrics.add("vObj", R.Engine.vObj, false, "#"); 158 R.debug.Metrics.add("rObj", R.Engine.rObjs, false, "#"); 159 R.debug.Metrics.add("droppedFrames", R.Engine.droppedFrames, false, "#"); 160 R.debug.Metrics.add("upTime", Math.floor((R.Engine.worldTime - R.Engine.upTime) / 1000), false, "# sec"); 161 R.debug.Metrics.add("pclRebuilds", R.Engine.pclRebuilds, false, "#"); 162 163 R.debug.Metrics.update(); 164 R.debug.Metrics.lastMetricSample = R.debug.Metrics.metricSampleRate; 165 } 166 167 if (R.debug.Metrics.showMetricsProfile && R.engine.Support.sysInfo().browser == "msie" && 168 parseFloat(R.engine.Support.sysInfo().version) < 9) { 169 // Profiler not supported in IE 170 R.debug.Metrics.showMetricsProfile = false; 171 } 172 173 if (R.debug.Metrics.showMetricsProfile && !R.debug.Metrics.profileDisplay) { 174 R.debug.Metrics.profileDisplay = $("<canvas width='150' height='100'/>").addClass("engine-profile"); 175 R.debug.Metrics.profileDisplay.appendTo($("body")); 176 R.debug.Metrics.profileDisplay[0].getContext('2d').save(); 177 } 178 }, 179 180 /** 181 * Set the interval at which metrics are sampled by the system. 182 * The default is for metrics to be calculated every 10 engine frames. 183 * 184 * @param sampleRate {Number} The number of ticks between samples 185 * @memberOf R.debug.Metrics 186 */ 187 setSampleRate:function (sampleRate) { 188 R.debug.Metrics.lastMetricSample = 1; 189 R.debug.Metrics.metricSampleRate = sampleRate; 190 }, 191 192 /** 193 * Add a metric to the game engine that can be displayed 194 * while it is running. If smoothing is selected, a 3 point 195 * running average will be used to smooth out jitters in the 196 * value that is shown. For the <tt>fmt</tt> argument, 197 * you can provide a string which contains the pound sign "#" 198 * that will be used to determine where the calculated value will 199 * occur in the formatted string. 200 * 201 * @param metricName {String} The name of the metric to track 202 * @param value {String/Number} The value of the metric. 203 * @param smoothing {Boolean} <tt>true</tt> to use 3 point average smoothing 204 * @param fmt {String} The way the value should be formatted in the display (e.g. "#ms") 205 * @memberOf R.debug.Metrics 206 */ 207 add:function (metricName, value, smoothing, fmt) { 208 if (smoothing) { 209 var vals = R.debug.Metrics.metrics[metricName] ? R.debug.Metrics.metrics[metricName].values : []; 210 if (vals.length == 0) { 211 // Init 212 vals.push(value); 213 vals.push(value); 214 vals.push(value); 215 } 216 vals.shift(); 217 vals.push(value); 218 var v = Math.floor((vals[0] + vals[1] + vals[2]) * 0.33); 219 R.debug.Metrics.metrics[metricName] = { val:(fmt ? fmt.replace("#", v) : v), values:vals, act:v }; 220 } else { 221 R.debug.Metrics.metrics[metricName] = { val:(fmt ? fmt.replace("#", value) : value), act:value }; 222 } 223 }, 224 225 /** 226 * Remove a metric from the display 227 * 228 * @param metricName {String} The name of the metric to remove 229 * @memberOf R.debug.Metrics 230 */ 231 remove:function (metricName) { 232 R.debug.Metrics.metrics[metricName] = null; 233 delete R.debug.Metrics.metrics[metricName]; 234 }, 235 236 /** 237 * Updates the display of the metrics window. 238 * @private 239 * @memberOf R.debug.Metrics 240 */ 241 update:function () { 242 var h = "", ctx; 243 if (R.debug.Metrics.showMetricsProfile) { 244 ctx = R.debug.Metrics.profileDisplay[0].getContext('2d'); 245 ctx.save(); 246 ctx.translate(147, 0); 247 } 248 249 for (var m in R.debug.Metrics.metrics) { 250 if (R.debug.Metrics.showMetricsWindow) { 251 h += m + ": " + R.debug.Metrics.metrics[m].val + "<br/>"; 252 } 253 if (R.debug.Metrics.showMetricsProfile) { 254 switch (m) { 255 case "engineLoad": 256 this.drawProfilePoint("#ffff00", R.debug.Metrics.metrics[m].act); 257 break; 258 case "vObj": 259 this.drawProfilePoint("#339933", R.debug.Metrics.metrics[m].act); 260 break; 261 case "rObj": 262 this.drawProfilePoint("#ff00ff", R.debug.Metrics.metrics[m].act); 263 break; 264 case "poolLoad" : 265 this.drawProfilePoint("#a0a0ff", R.debug.Metrics.metrics[m].act); 266 break; 267 } 268 } 269 } 270 if (R.debug.Metrics.showMetricsWindow) { 271 $(".items", R.debug.Metrics.metricDisplay).html(h); 272 } 273 if (R.debug.Metrics.showMetricsProfile) { 274 ctx.restore(); 275 R.debug.Metrics.moveProfiler(); 276 } 277 }, 278 279 /** 280 * @private 281 */ 282 drawProfilePoint:function (color, val) { 283 var ctx = R.debug.Metrics.profileDisplay[0].getContext('2d'); 284 ctx.strokeStyle = color; 285 try { 286 if (!isNaN(val)) { 287 ctx.beginPath(); 288 ctx.moveTo(0, R.debug.Metrics.profiles[color] || 100); 289 ctx.lineTo(1, (100 - val < 1 ? 1 : 100 - val)); 290 ctx.closePath(); 291 ctx.stroke(); 292 R.debug.Metrics.profiles[color] = (100 - val < 1 ? 1 : 100 - val); 293 } 294 } catch (ex) { 295 296 } 297 }, 298 299 /** 300 * @private 301 */ 302 moveProfiler:function () { 303 var ctx = R.debug.Metrics.profileDisplay[0].getContext('2d'); 304 var imgData = ctx.getImageData(1, 0, 149, 100); 305 ctx.save(); 306 ctx.translate(-1, 0); 307 ctx.putImageData(imgData, 0, 0); 308 ctx.restore(); 309 }, 310 311 /** 312 * Run the metrics display. 313 * @private 314 * @memberOf R.debug.Metrics 315 */ 316 doMetrics:function () { 317 // Output any metrics 318 if (R.debug.Metrics.showMetricsWindow || R.debug.Metrics.showMetricsProfile) { 319 R.debug.Metrics.render(); 320 } else if (!R.debug.Metrics.showMetricsWindow && R.debug.Metrics.metricDisplay) { 321 R.debug.Metrics.metricDisplay.remove(); 322 R.debug.Metrics.metricDisplay = null; 323 } 324 } 325 326 }); 327 328 if (R.engine.Support.checkBooleanParam("metrics")) { 329 R.debug.Metrics.showMetrics(); 330 } 331 332 if (R.engine.Support.checkBooleanParam("profile")) { 333 R.debug.Metrics.showProfile(); 334 } 335 336