diff options
Diffstat (limited to 'testing/talos/talos/tests/webgl/benchmarks/terrain')
-rw-r--r-- | testing/talos/talos/tests/webgl/benchmarks/terrain/grass.jpeg | bin | 0 -> 45396 bytes | |||
-rw-r--r-- | testing/talos/talos/tests/webgl/benchmarks/terrain/perftest.html | 407 |
2 files changed, 407 insertions, 0 deletions
diff --git a/testing/talos/talos/tests/webgl/benchmarks/terrain/grass.jpeg b/testing/talos/talos/tests/webgl/benchmarks/terrain/grass.jpeg Binary files differnew file mode 100644 index 0000000000..641daff112 --- /dev/null +++ b/testing/talos/talos/tests/webgl/benchmarks/terrain/grass.jpeg 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..7a8c33b187 --- /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.resume("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.pause("Stopping 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> |