summaryrefslogtreecommitdiffstats
path: root/testing/talos/talos/tests/webgl/benchmarks/terrain/perftest.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/talos/talos/tests/webgl/benchmarks/terrain/perftest.html')
-rw-r--r--testing/talos/talos/tests/webgl/benchmarks/terrain/perftest.html407
1 files changed, 407 insertions, 0 deletions
diff --git a/testing/talos/talos/tests/webgl/benchmarks/terrain/perftest.html b/testing/talos/talos/tests/webgl/benchmarks/terrain/perftest.html
new file mode 100644
index 0000000000..a472162552
--- /dev/null
+++ b/testing/talos/talos/tests/webgl/benchmarks/terrain/perftest.html
@@ -0,0 +1,407 @@
+<html>
+<!--
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+ Version 1.1
+ - Added viewport rotation.
+ - Adapted for talos with with 4 runs on combinations of alpha/antialias
+
+ Version 1.0
+ - Benoit Jacob's WebGL tutorial demos: http://bjacob.github.io/webgl-tutorial/12-texture.html
+-->
+<head>
+<meta charset="utf-8"/>
+<script src="resource://talos-powers/TalosContentProfiler.js"></script>
+<script type="x-shader/x-vertex" id="vertexShader">
+ attribute vec3 vertexPosition;
+ attribute vec3 normalVector;
+ attribute vec2 textureCoord;
+ uniform mat4 modelview;
+ uniform mat4 projection;
+ varying vec3 varyingNormalVector;
+ varying vec2 varyingTextureCoord;
+ void main(void) {
+ gl_Position = projection * modelview * vec4(vertexPosition, 1.0);
+ varyingNormalVector = normalVector;
+ varyingTextureCoord = textureCoord;
+ }
+</script>
+<script type="x-shader/x-fragment" id="fragmentShader">
+ precision mediump float;
+ varying vec3 varyingNormalVector;
+ uniform vec3 lightDirection;
+ uniform sampler2D grassTextureSampler;
+ varying vec2 varyingTextureCoord;
+ void main(void) {
+ vec3 grassColor = texture2D(grassTextureSampler, varyingTextureCoord).rgb;
+ const float ambientLight = 0.3;
+ const float diffuseLight = 0.7;
+ float c = clamp(dot(normalize(varyingNormalVector), lightDirection), 0.0, 1.0);
+ vec3 resultColor = grassColor * (c * diffuseLight + ambientLight);
+ gl_FragColor = vec4(resultColor, 1);
+ }
+</script>
+<script>
+ var gl;
+
+ var modelviewUniformLoc;
+ var projectionUniformLoc;
+ var lightDirectionUniformLoc;
+ var grassTextureSamplerUniformLoc;
+
+ var modelviewMatrix = new Float32Array(16);
+ var projectionMatrix = new Float32Array(16);
+
+ var terrainSize = 32;
+ var aspectRatio;
+
+ function startTest(alpha, antialias, doneCallback) {
+ gl = document.getElementById("c").getContext("webgl", {alpha, antialias});
+
+ var grassImage = document.getElementById("grass");
+ var grassTexture = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, grassTexture);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, grassImage);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
+ gl.generateMipmap(gl.TEXTURE_2D);
+
+ var vertexShader = gl.createShader(gl.VERTEX_SHADER);
+ var vertexShaderString = document.getElementById("vertexShader").text;
+ gl.shaderSource(vertexShader, vertexShaderString);
+ gl.compileShader(vertexShader);
+
+ var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
+ var fragmentShaderString = document.getElementById("fragmentShader").text;
+ gl.shaderSource(fragmentShader, fragmentShaderString);
+ gl.compileShader(fragmentShader);
+
+ var program = gl.createProgram();
+ gl.attachShader(program, vertexShader);
+ gl.attachShader(program, fragmentShader);
+ gl.linkProgram(program);
+ gl.useProgram(program);
+
+ var vertexPositionAttrLoc = gl.getAttribLocation(program, "vertexPosition");
+ gl.enableVertexAttribArray(vertexPositionAttrLoc);
+ var normalVectorAttrLoc = gl.getAttribLocation(program, "normalVector");
+ gl.enableVertexAttribArray(normalVectorAttrLoc);
+ var textureCoordAttrLoc = gl.getAttribLocation(program, "textureCoord");
+ gl.enableVertexAttribArray(textureCoordAttrLoc);
+
+ modelviewUniformLoc = gl.getUniformLocation(program, "modelview");
+ projectionUniformLoc = gl.getUniformLocation(program, "projection");
+ lightDirectionUniformLoc = gl.getUniformLocation(program, "lightDirection");
+ grassTextureSamplerUniformLoc = gl.getUniformLocation(program, "grassTextureSampler");
+
+ var vertices = new Float32Array(terrainSize * terrainSize * 3);
+ var normalVectors = new Float32Array(terrainSize * terrainSize * 3);
+ var textureCoords = new Float32Array(terrainSize * terrainSize * 2);
+
+ for (var i = 0; i < terrainSize; i++) {
+ for (var j = 0; j < terrainSize; j++) {
+ var a = 2 * Math.PI * i / terrainSize;
+ var b = 2 * Math.PI * j / terrainSize;
+ var height = 4 * Math.cos(a) + 6 * Math.sin(b) + Math.cos(4 * a) + Math.sin(5 * b);
+ vertices[3 * (i + terrainSize * j) + 0] = i;
+ vertices[3 * (i + terrainSize * j) + 1] = height;
+ vertices[3 * (i + terrainSize * j) + 2] = j;
+
+ var d_y_d_x = (2 * Math.PI / terrainSize) * (-4 * Math.sin(a) - 4 * Math.sin(4 * a));
+ var d_y_d_z = (2 * Math.PI / terrainSize) * (6 * Math.cos(b) + 5 * Math.cos(5 * b));
+ var normal_x = d_y_d_x;
+ var normal_y = -1;
+ var normal_z = d_y_d_z;
+ var normal_length = Math.sqrt(normal_x * normal_x + normal_y * normal_y + normal_z * normal_z);
+ normalVectors[3 * (i + terrainSize * j) + 0] = normal_x / normal_length;
+ normalVectors[3 * (i + terrainSize * j) + 1] = normal_y / normal_length;
+ normalVectors[3 * (i + terrainSize * j) + 2] = normal_z / normal_length;
+
+ var textureRepeatingSpeed = 0.5;
+ textureCoords[2 * (i + terrainSize * j) + 0] = i * textureRepeatingSpeed;
+ textureCoords[2 * (i + terrainSize * j) + 1] = j * textureRepeatingSpeed;
+ }
+ }
+
+ var vertexPositionBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
+ gl.vertexAttribPointer(vertexPositionAttrLoc, 3, gl.FLOAT, false, 0, 0);
+
+ var normalVectorBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, normalVectorBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, normalVectors, gl.STATIC_DRAW);
+ gl.vertexAttribPointer(normalVectorAttrLoc, 3, gl.FLOAT, false, 0, 0);
+
+ var textureCoordBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, textureCoords, gl.STATIC_DRAW);
+ gl.vertexAttribPointer(textureCoordAttrLoc, 2, gl.FLOAT, false, 0, 0);
+
+ var indexBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
+ var indices = [];
+ for (var k = 0; k < terrainSize - 1; k++) {
+ for (var l = 0; l < terrainSize - 1; l++) {
+ indices.push(k + terrainSize * l);
+ indices.push(k + 1 + terrainSize * l);
+ indices.push(k + 1 + terrainSize * (l + 1));
+
+ indices.push(k + terrainSize * l);
+ indices.push(k + 1 + terrainSize * (l + 1));
+ indices.push(k + terrainSize * (l + 1));
+ }
+ }
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
+
+ gl.enable(gl.DEPTH_TEST);
+
+ onresize();
+ window.addEventListener("resize", onresize);
+
+ startTestLoop(doneCallback);
+ }
+
+ function average(arr) {
+ var sum = 0;
+ for (var i in arr)
+ sum += arr[i];
+ return sum / (arr.length || 1);
+ }
+
+ // We use sample stddev and not population stddev because
+ // well.. it's a sample and we can't collect all/infinite number of frames.
+ function sampleStdDev(arr) {
+ if (arr.length <= 1) {
+ return 0;
+ }
+ var avg = average(arr);
+
+ var squareDiffArr = arr.map( function(v) { return Math.pow(v - avg, 2); } );
+ var sum = squareDiffArr.reduce( function(a, b) { return a + b; } );
+ var rv = Math.sqrt(sum / (arr.length - 1));
+ return rv;
+ }
+
+ const PRETEST_DELAY_MS = 500;
+ const WARMUP_TIMESTAMPS = 30; // Must be at least 2
+ const MEASURED_FRAMES = 100;
+
+ var gDoneCallback = function placeholder(intervals) {};
+ var gCurrentTimestamp = 0;
+ var gResultTimestamps = [];
+
+ function startTestLoop(doneCallback) {
+ gDoneCallback = doneCallback;
+ gCurrentTimestamp = 0;
+ gResultTimestamps = new Array(WARMUP_TIMESTAMPS + MEASURED_FRAMES);
+
+ TalosContentProfiler.subtestStart("Starting requestAnimationFrame loop", true).then(() => {
+ requestAnimationFrame(draw);
+ });
+ }
+
+ function draw(timestamp) {
+ // It's possible that under some implementations (even if not our current one),
+ // the rAF callback arg will be in some way "optimized", e.g. always point to the
+ // estimated next vsync timestamp, in order to allow the callee to have less
+ // jitter in its time-dependent positioning/processing.
+ // Such behaviour would harm our measurements, especially with vsync off.
+ // performance.now() will not have this potential issue and is high-resolution.
+ gResultTimestamps[gCurrentTimestamp++] = performance.now();
+ var recordedTimestamps = gCurrentTimestamp;
+ if (recordedTimestamps >= WARMUP_TIMESTAMPS + MEASURED_FRAMES) {
+ TalosContentProfiler.subtestEnd("requestAnimationFrame loop", true).then(() => {
+ var intervals = [];
+ var lastWarmupTimestampId = WARMUP_TIMESTAMPS - 1;
+ for (var i = lastWarmupTimestampId + 1; i < gResultTimestamps.length; i++) {
+ intervals.push(gResultTimestamps[i] - gResultTimestamps[i - 1]);
+ }
+
+ gDoneCallback(intervals);
+ });
+
+ return;
+ }
+
+ // Used for rendering reproducible frames which are independent of actual performance (timestamps).
+ var simulatedTimestamp = gCurrentTimestamp * 1000 / 60;
+
+ var speed = 0.001;
+ var angle = simulatedTimestamp * speed;
+ var c = Math.cos(angle / 10);
+ var s = Math.sin(angle / 10);
+ var light_x = Math.cos(angle);
+ var light_y = -1;
+ var light_z = Math.sin(angle);
+ var l = Math.sqrt(light_x * light_x + light_y * light_y + light_z * light_z);
+ light_x /= l;
+ light_y /= l;
+ light_z /= l;
+ gl.uniform3f(lightDirectionUniformLoc, light_x, light_y, light_z);
+
+ modelviewMatrix[0] = c;
+ modelviewMatrix[1] = 0;
+ modelviewMatrix[2] = s;
+ modelviewMatrix[3] = 0;
+
+ modelviewMatrix[4] = 0;
+ modelviewMatrix[5] = 1;
+ modelviewMatrix[6] = 0;
+ modelviewMatrix[7] = 0;
+
+ modelviewMatrix[8] = -s;
+ modelviewMatrix[9] = 0;
+ modelviewMatrix[10] = c;
+ modelviewMatrix[11] = 0;
+
+ modelviewMatrix[12] = -terrainSize / 2;
+ modelviewMatrix[13] = 0;
+ modelviewMatrix[14] = -terrainSize;
+ modelviewMatrix[15] = 1;
+
+ gl.uniformMatrix4fv(modelviewUniformLoc, false, modelviewMatrix);
+
+ var fieldOfViewY = Math.PI / 4;
+ aspectRatio = gl.drawingBufferWidth / gl.drawingBufferHeight;
+ var zNear = 1;
+ var zFar = terrainSize;
+
+ projectionMatrix[0] = fieldOfViewY / aspectRatio;
+ projectionMatrix[1] = 0;
+ projectionMatrix[2] = 0;
+ projectionMatrix[3] = 0;
+
+ projectionMatrix[4] = 0;
+ projectionMatrix[5] = fieldOfViewY;
+ projectionMatrix[6] = 0;
+ projectionMatrix[7] = 0;
+
+ projectionMatrix[8] = 0;
+ projectionMatrix[9] = 0;
+ projectionMatrix[10] = (zNear + zFar) / (zNear - zFar);
+ projectionMatrix[11] = -1;
+
+ projectionMatrix[12] = 0;
+ projectionMatrix[13] = 0;
+ projectionMatrix[14] = 2 * zNear * zFar / (zNear - zFar);
+ projectionMatrix[15] = 0;
+
+ gl.uniformMatrix4fv(projectionUniformLoc, false, projectionMatrix);
+
+ gl.uniform1i(grassTextureSamplerUniformLoc, 0);
+
+ gl.clearColor(0.4, 0.6, 1.0, 0.5);
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ gl.drawElements(gl.TRIANGLES, (terrainSize - 1) * (terrainSize - 1) * 6, gl.UNSIGNED_SHORT, 0);
+
+ if (gCurrentTimestamp == 1) {
+ // First frame only - wait a bit (after rendering the scene once)
+ requestAnimationFrame(function() {
+ setTimeout(requestAnimationFrame, PRETEST_DELAY_MS, draw);
+ });
+ } else {
+ requestAnimationFrame(draw);
+ }
+ }
+
+ function onresize() {
+ var canvas = document.getElementById("c");
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ aspectRatio = canvas.width / canvas.height;
+ gl.viewport(0, 0, canvas.width, canvas.height);
+ }
+
+ var gResults = {values: [], names: [], raw: []};
+
+ function setupAndRun(alpha, antialias, name, doneCallback) {
+ // Remove the old canvas if exists, and create a new one for the new run
+ var c = document.getElementById("c");
+ if (c)
+ c.remove();
+ c = document.createElement("canvas");
+ c.id = "c";
+ document.body.insertBefore(c, document.body.firstChild);
+
+ // Trigger the run with specified args, with callback function to process the result
+ startTest(alpha, antialias, function(intervals) {
+ gResults.names.push(name);
+ gResults.raw.push(intervals);
+ setTimeout(doneCallback, 0);
+ });
+ }
+
+ function reportResults(results) {
+ // Format a nice human-readable report
+ var msg = "";
+ for (var i = 0; i < results.names.length; i++) {
+ var data = results.raw[i];
+ var avg = average(data);
+ gResults.values.push(avg); // This is the only "official" reported value for this set
+ var sd = sampleStdDev(data);
+
+ // Compose a nice human readable message. Not used officially anywhere.
+ msg += results.names[i] + "= Average: " + avg.toFixed(2)
+ + " stddev: " + sd.toFixed(1) + " (" + (100 * sd / avg).toFixed(1) + "%)"
+ + "\nIntervals: " + data.map(function(v) {
+ // Display with 1 decimal point digit, and make excessively big values noticeable.
+ var value = v.toFixed(1);
+ // Not scientific, but intervals over 2 * average are.. undesirable.
+ var threshold = avg * 2;
+ return v < threshold ? value : " [_" + value + "_] ";
+ }).join(" ")
+ + "\n\n";
+ }
+ dump(msg); // Put the readable report at talos run-log
+
+ if (window.tpRecordTime) {
+ // within talos - report the results
+ return tpRecordTime(results.values.join(","), 0, results.names.join(","));
+ }
+ // Local run in a plain browser, display the formatted report
+ alert(msg);
+ return undefined;
+ }
+
+ // The full test starts here
+ function test() {
+ // We initially hide the <body>, to reduce the chance of spinning our wheels
+ // with incremental ASAP-paint-mode paints during pageload. Now that onload
+ // has fired, we un-hide it:
+ document.body.style.display = "";
+
+ gResults = {values: [], names: [], raw: []};
+ // This test measures average frame interval during WebGL animation as follows:
+ // 1. Creates a new WebGL canvas.
+ // 2. setup the scene and render 1 frame.
+ // 3. Idle a bit (500ms).
+ // 4. Render/Animate several (129) frames using requestAnimationFrame.
+ // 5. Average the intervals of the last (100) frames <-- the result.
+ //
+ // The reason for the initial idle is to allow some internal cleanups (like CC/GC etc).
+ // The unmeasured warmup rendering intervals are to allow Firefox to settle at a consistent rate.
+ // The idle + warmup are common practices in benchmarking where we're mostly interested
+ // in the stable/consistent throughput rather than in the throughput during transition state
+ // from idle to iterating.
+
+ // Run the same sequence 4 times for all combination of alpha and antialias
+ // (Not using promises chaining for better compatibility, ending up with nesting instead)
+ // talos unfortunately trims common prefixes, so we prefix with a running number to keep the full name
+ setupAndRun(false, false, "0.WebGL-terrain-alpha-no-AA-no", function() {
+ setupAndRun(false, true, "1.WebGL-terrain-alpha-no-AA-yes", function() {
+ setupAndRun(true, false, "2.WebGL-terrain-alpha-yes-AA-no", function() {
+ setupAndRun(true, true, "3.WebGL-terrain-alpha-yes-AA-yes", function() {
+ reportResults(gResults);
+ });
+ });
+ });
+ });
+ }
+</script>
+</head>
+<body onload="test();" style="overflow:hidden; margin:0; display:none">
+<canvas id="c"></canvas>
+<img src="grass.jpeg" style="display:none" id="grass"/>
+</body>