1// Copyright 2011 the V8 project authors. All rights reserved. 2// Redistribution and use in source and binary forms, with or without 3// modification, are permitted provided that the following conditions are 4// met: 5// 6// * Redistributions of source code must retain the above copyright 7// notice, this list of conditions and the following disclaimer. 8// * Redistributions in binary form must reproduce the above 9// copyright notice, this list of conditions and the following 10// disclaimer in the documentation and/or other materials provided 11// with the distribution. 12// * Neither the name of Google Inc. nor the names of its 13// contributors may be used to endorse or promote products derived 14// from this software without specific prior written permission. 15// 16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28 29/** 30 * This function provides requestAnimationFrame in a cross browser way. 31 * http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 32 */ 33if ( !window.requestAnimationFrame ) { 34 window.requestAnimationFrame = ( function() { 35 return window.webkitRequestAnimationFrame || 36 window.mozRequestAnimationFrame || 37 window.oRequestAnimationFrame || 38 window.msRequestAnimationFrame || 39 function(callback, element) { 40 window.setTimeout( callback, 1000 / 60 ); 41 }; 42 } )(); 43} 44 45var kNPoints = 8000; 46var kNModifications = 20; 47var kNVisiblePoints = 200; 48var kDecaySpeed = 20; 49 50var kPointRadius = 4; 51var kInitialLifeForce = 100; 52 53var livePoints = void 0; 54var dyingPoints = void 0; 55var scene = void 0; 56var renderingStartTime = void 0; 57var scene = void 0; 58var pausePlot = void 0; 59var splayTree = void 0; 60var numberOfFrames = 0; 61var sumOfSquaredPauses = 0; 62var benchmarkStartTime = void 0; 63var benchmarkTimeLimit = void 0; 64var autoScale = void 0; 65var pauseDistribution = []; 66 67 68function Point(x, y, z, payload) { 69 this.x = x; 70 this.y = y; 71 this.z = z; 72 73 this.next = null; 74 this.prev = null; 75 this.payload = payload; 76 this.lifeForce = kInitialLifeForce; 77} 78 79 80Point.prototype.color = function () { 81 return "rgba(0, 0, 0, " + (this.lifeForce / kInitialLifeForce) + ")"; 82}; 83 84 85Point.prototype.decay = function () { 86 this.lifeForce -= kDecaySpeed; 87 return this.lifeForce <= 0; 88}; 89 90 91function PointsList() { 92 this.head = null; 93 this.count = 0; 94} 95 96 97PointsList.prototype.add = function (point) { 98 if (this.head !== null) this.head.prev = point; 99 point.next = this.head; 100 this.head = point; 101 this.count++; 102} 103 104 105PointsList.prototype.remove = function (point) { 106 if (point.next !== null) { 107 point.next.prev = point.prev; 108 } 109 if (point.prev !== null) { 110 point.prev.next = point.next; 111 } else { 112 this.head = point.next; 113 } 114 this.count--; 115} 116 117 118function GeneratePayloadTree(depth, tag) { 119 if (depth == 0) { 120 return { 121 array : [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], 122 string : 'String for key ' + tag + ' in leaf node' 123 }; 124 } else { 125 return { 126 left: GeneratePayloadTree(depth - 1, tag), 127 right: GeneratePayloadTree(depth - 1, tag) 128 }; 129 } 130} 131 132 133// To make the benchmark results predictable, we replace Math.random 134// with a 100% deterministic alternative. 135Math.random = (function() { 136 var seed = 49734321; 137 return function() { 138 // Robert Jenkins' 32 bit integer hash function. 139 seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff; 140 seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff; 141 seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff; 142 seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; 143 seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff; 144 seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff; 145 return (seed & 0xfffffff) / 0x10000000; 146 }; 147})(); 148 149 150function GenerateKey() { 151 // The benchmark framework guarantees that Math.random is 152 // deterministic; see base.js. 153 return Math.random(); 154} 155 156function CreateNewPoint() { 157 // Insert new node with a unique key. 158 var key; 159 do { key = GenerateKey(); } while (splayTree.find(key) != null); 160 161 var point = new Point(Math.random() * 40 - 20, 162 Math.random() * 40 - 20, 163 Math.random() * 40 - 20, 164 GeneratePayloadTree(5, "" + key)); 165 166 livePoints.add(point); 167 168 splayTree.insert(key, point); 169 return key; 170} 171 172function ModifyPointsSet() { 173 if (livePoints.count < kNPoints) { 174 for (var i = 0; i < kNModifications; i++) { 175 CreateNewPoint(); 176 } 177 } else if (kNModifications === 20) { 178 kNModifications = 80; 179 kDecay = 30; 180 } 181 182 for (var i = 0; i < kNModifications; i++) { 183 var key = CreateNewPoint(); 184 var greatest = splayTree.findGreatestLessThan(key); 185 if (greatest == null) { 186 var point = splayTree.remove(key).value; 187 } else { 188 var point = splayTree.remove(greatest.key).value; 189 } 190 livePoints.remove(point); 191 point.payload = null; 192 dyingPoints.add(point); 193 } 194} 195 196 197function PausePlot(width, height, size, scale) { 198 var canvas = document.createElement("canvas"); 199 canvas.width = this.width = width; 200 canvas.height = this.height = height; 201 document.body.appendChild(canvas); 202 203 this.ctx = canvas.getContext('2d'); 204 205 if (typeof scale !== "number") { 206 this.autoScale = true; 207 this.maxPause = 0; 208 } else { 209 this.autoScale = false; 210 this.maxPause = scale; 211 } 212 213 this.size = size; 214 215 // Initialize cyclic buffer for pauses. 216 this.pauses = new Array(this.size); 217 this.start = this.size; 218 this.idx = 0; 219} 220 221 222PausePlot.prototype.addPause = function (p) { 223 if (this.idx === this.size) { 224 this.idx = 0; 225 } 226 227 if (this.idx === this.start) { 228 this.start++; 229 } 230 231 if (this.start === this.size) { 232 this.start = 0; 233 } 234 235 this.pauses[this.idx++] = p; 236}; 237 238 239PausePlot.prototype.iteratePauses = function (f) { 240 if (this.start < this.idx) { 241 for (var i = this.start; i < this.idx; i++) { 242 f.call(this, i - this.start, this.pauses[i]); 243 } 244 } else { 245 for (var i = this.start; i < this.size; i++) { 246 f.call(this, i - this.start, this.pauses[i]); 247 } 248 249 var offs = this.size - this.start; 250 for (var i = 0; i < this.idx; i++) { 251 f.call(this, i + offs, this.pauses[i]); 252 } 253 } 254}; 255 256 257PausePlot.prototype.draw = function () { 258 var first = null; 259 260 if (this.autoScale) { 261 this.iteratePauses(function (i, v) { 262 if (first === null) { 263 first = v; 264 } 265 this.maxPause = Math.max(v, this.maxPause); 266 }); 267 } 268 269 var dx = this.width / this.size; 270 var dy = this.height / this.maxPause; 271 272 this.ctx.save(); 273 this.ctx.clearRect(0, 0, this.width, this.height); 274 this.ctx.beginPath(); 275 this.ctx.moveTo(1, dy * this.pauses[this.start]); 276 var p = first; 277 this.iteratePauses(function (i, v) { 278 var delta = v - p; 279 var x = 1 + dx * i; 280 var y = dy * v; 281 this.ctx.lineTo(x, y); 282 if (delta > 2 * (p / 3)) { 283 this.ctx.font = "bold 12px sans-serif"; 284 this.ctx.textBaseline = "bottom"; 285 this.ctx.fillText(v + "ms", x + 2, y); 286 } 287 p = v; 288 }); 289 this.ctx.strokeStyle = "black"; 290 this.ctx.stroke(); 291 this.ctx.restore(); 292} 293 294 295function Scene(width, height) { 296 var canvas = document.createElement("canvas"); 297 canvas.width = width; 298 canvas.height = height; 299 document.body.appendChild(canvas); 300 301 this.ctx = canvas.getContext('2d'); 302 this.width = canvas.width; 303 this.height = canvas.height; 304 305 // Projection configuration. 306 this.x0 = canvas.width / 2; 307 this.y0 = canvas.height / 2; 308 this.z0 = 100; 309 this.f = 1000; // Focal length. 310 311 // Camera is rotating around y-axis. 312 this.angle = 0; 313} 314 315 316Scene.prototype.drawPoint = function (x, y, z, color) { 317 // Rotate the camera around y-axis. 318 var rx = x * Math.cos(this.angle) - z * Math.sin(this.angle); 319 var ry = y; 320 var rz = x * Math.sin(this.angle) + z * Math.cos(this.angle); 321 322 // Perform perspective projection. 323 var px = (this.f * rx) / (rz - this.z0) + this.x0; 324 var py = (this.f * ry) / (rz - this.z0) + this.y0; 325 326 this.ctx.save(); 327 this.ctx.fillStyle = color 328 this.ctx.beginPath(); 329 this.ctx.arc(px, py, kPointRadius, 0, 2 * Math.PI, true); 330 this.ctx.fill(); 331 this.ctx.restore(); 332}; 333 334 335Scene.prototype.drawDyingPoints = function () { 336 var point_next = null; 337 for (var point = dyingPoints.head; point !== null; point = point_next) { 338 // Rotate the scene around y-axis. 339 scene.drawPoint(point.x, point.y, point.z, point.color()); 340 341 point_next = point.next; 342 343 // Decay the current point and remove it from the list 344 // if it's life-force ran out. 345 if (point.decay()) { 346 dyingPoints.remove(point); 347 } 348 } 349}; 350 351 352Scene.prototype.draw = function () { 353 this.ctx.save(); 354 this.ctx.clearRect(0, 0, this.width, this.height); 355 this.drawDyingPoints(); 356 this.ctx.restore(); 357 358 this.angle += Math.PI / 90.0; 359}; 360 361 362function updateStats(pause) { 363 numberOfFrames++; 364 if (pause > 20) { 365 sumOfSquaredPauses += (pause - 20) * (pause - 20); 366 } 367 pauseDistribution[Math.floor(pause / 10)] |= 0; 368 pauseDistribution[Math.floor(pause / 10)]++; 369} 370 371 372function renderStats() { 373 var msg = document.createElement("p"); 374 msg.innerHTML = "Score " + 375 Math.round(numberOfFrames * 1000 / sumOfSquaredPauses); 376 var table = document.createElement("table"); 377 table.align = "center"; 378 for (var i = 0; i < pauseDistribution.length; i++) { 379 if (pauseDistribution[i] > 0) { 380 var row = document.createElement("tr"); 381 var time = document.createElement("td"); 382 var count = document.createElement("td"); 383 time.innerHTML = i*10 + "-" + (i+1)*10 + "ms"; 384 count.innerHTML = " => " + pauseDistribution[i]; 385 row.appendChild(time); 386 row.appendChild(count); 387 table.appendChild(row); 388 } 389 } 390 div.appendChild(msg); 391 div.appendChild(table); 392} 393 394 395function render() { 396 if (typeof renderingStartTime === 'undefined') { 397 renderingStartTime = Date.now(); 398 benchmarkStartTime = renderingStartTime; 399 } 400 401 ModifyPointsSet(); 402 403 scene.draw(); 404 405 var renderingEndTime = Date.now(); 406 var pause = renderingEndTime - renderingStartTime; 407 pausePlot.addPause(pause); 408 renderingStartTime = renderingEndTime; 409 410 pausePlot.draw(); 411 412 updateStats(pause); 413 414 div.innerHTML = 415 livePoints.count + "/" + dyingPoints.count + " " + 416 pause + "(max = " + pausePlot.maxPause + ") ms " + 417 numberOfFrames + " frames"; 418 419 if (renderingEndTime < benchmarkStartTime + benchmarkTimeLimit) { 420 // Schedule next frame. 421 requestAnimationFrame(render); 422 } else { 423 renderStats(); 424 } 425} 426 427 428function Form() { 429 function create(tag) { return document.createElement(tag); } 430 function text(value) { return document.createTextNode(value); } 431 432 this.form = create("form"); 433 this.form.setAttribute("action", "javascript:start()"); 434 435 var table = create("table"); 436 table.setAttribute("style", "margin-left: auto; margin-right: auto;"); 437 438 function col(a) { 439 var td = create("td"); 440 td.appendChild(a); 441 return td; 442 } 443 444 function row(a, b) { 445 var tr = create("tr"); 446 tr.appendChild(col(a)); 447 tr.appendChild(col(b)); 448 return tr; 449 } 450 451 this.timelimit = create("input"); 452 this.timelimit.setAttribute("value", "60"); 453 454 table.appendChild(row(text("Time limit in seconds"), this.timelimit)); 455 456 this.autoscale = create("input"); 457 this.autoscale.setAttribute("type", "checkbox"); 458 this.autoscale.setAttribute("checked", "true"); 459 table.appendChild(row(text("Autoscale pauses plot"), this.autoscale)); 460 461 var button = create("input"); 462 button.setAttribute("type", "submit"); 463 button.setAttribute("value", "Start"); 464 this.form.appendChild(table); 465 this.form.appendChild(button); 466 467 document.body.appendChild(this.form); 468} 469 470 471Form.prototype.remove = function () { 472 document.body.removeChild(this.form); 473}; 474 475 476function init() { 477 livePoints = new PointsList; 478 dyingPoints = new PointsList; 479 480 splayTree = new SplayTree(); 481 482 scene = new Scene(640, 480); 483 484 div = document.createElement("div"); 485 document.body.appendChild(div); 486 487 pausePlot = new PausePlot(480, autoScale ? 240 : 500, 160, autoScale ? void 0 : 500); 488} 489 490function start() { 491 benchmarkTimeLimit = form.timelimit.value * 1000; 492 autoScale = form.autoscale.checked; 493 form.remove(); 494 init(); 495 render(); 496} 497 498var form = new Form(); 499