blob: 5ae11948d6d6a4f92267edd0393b3750394eecf3 [file] [log] [blame]
Ben Murdoch3ef787d2012-04-12 10:51:47 +01001// 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();