| <!doctype html> |
| <!-- |
| Copyright 2016 The Chromium Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. |
| --> |
| <html> |
| <head> |
| <meta charset="utf-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> |
| <meta name="mobile-web-app-capable" content="yes"> |
| <meta name="apple-mobile-web-app-capable" content="yes"> |
| |
| <!-- Origin Trial Token, feature = WebVR (For Chrome M62+), origin = https://webvr.info, expires = 2018-09-10 --> |
| <meta http-equiv="origin-trial" data-feature="WebVR (For Chrome M62+)" data-expires="2018-09-10" content="AhQcOrbjvS0+50wwuqtAidzraKNfZj8Bj159g2+2LsT5QRHe9IeebCl5ApORwd3oGxfKzl5H8s5K3aTMNzC+5gsAAABPeyJvcmlnaW4iOiJodHRwczovL3dlYnZyLmluZm86NDQzIiwiZmVhdHVyZSI6IldlYlZSMS4xTTYyIiwiZXhwaXJ5IjoxNTM2NjAxNDEzfQ=="> |
| |
| |
| <title>TEST - Slow Rendering</title> |
| |
| <!-- |
| This test attempts to be GPU bound, so that WebVR implementations can |
| guage how well they handle that scenario. |
| --> |
| |
| <style> |
| #webgl-canvas { |
| box-sizing: border-box; |
| height: 100%; |
| left: 0; |
| margin: 0; |
| position: absolute; |
| top: 0; |
| width: 100%; |
| } |
| </style> |
| |
| <!-- This entire block in only to facilitate dynamically enabling and |
| disabling the WebVR polyfill, and is not necessary for most WebVR apps. |
| If you want to use the polyfill in your app, just include the js file and |
| everything will work the way you want it to by default. --> |
| <script> |
| var WebVRConfig = { |
| // Prevents the polyfill from initializing automatically. |
| DEFER_INITIALIZATION: true, |
| // Ensures the polyfill is always active when initialized, even if the |
| // native API is available. This is probably NOT what most pages want. |
| ALWAYS_APPEND_POLYFILL_DISPLAY: true, |
| // Polyfill optimizations |
| DIRTY_SUBMIT_FRAME_BINDINGS: true, |
| BUFFER_SCALE: 0.75, |
| }; |
| </script> |
| <script src="js/third-party/webvr-polyfill.js"></script> |
| <script src="js/third-party/wglu/wglu-url.js"></script> |
| <script> |
| // Dynamically turn the polyfill on if requested by the query args. |
| if (WGLUUrl.getBool('polyfill', false)) { |
| InitializeWebVRPolyfill(); |
| } else { |
| // Shim for migration from older version of WebVR. Shouldn't be necessary for very long. |
| InitializeSpecShim(); |
| } |
| </script> |
| <!-- End sample polyfill enabling logic --> |
| |
| <script src="js/third-party/gl-matrix-min.js"></script> |
| |
| <script src="js/third-party/wglu/wglu-program.js"></script> |
| <script src="js/third-party/wglu/wglu-stats.js"></script> |
| <script src="js/third-party/wglu/wglu-texture.js"></script> |
| |
| <script src="js/vr-cube-sea.js"></script> |
| <script src="js/vr-samples-util.js"></script> |
| </head> |
| <body> |
| <canvas id="webgl-canvas"></canvas> |
| <script> |
| /* global mat4, VRCubeSea, WGLUStats, WGLUTextureLoader, VRSamplesUtil */ |
| (function () { |
| "use strict"; |
| |
| var vrDisplay = null; |
| var frameData = null; |
| var projectionMat = mat4.create(); |
| var viewMat = mat4.create(); |
| var vrPresentButton = null; |
| |
| // If true, use a very heavyweight shader to stress the GPU. |
| var heavyGpu = WGLUUrl.getBool('heavyGpu', false); |
| |
| // Number and size of the static cubes. Warning, large values |
| // don't render right due to overflow of the int16 indices. |
| var cubeCount = WGLUUrl.getInt('cubeCount', heavyGpu ? 12 : 10); |
| var cubeScale = WGLUUrl.getFloat('cubeScale', 1.0); |
| |
| // Busy wait for the specified time to simulate CPU intensive JS. |
| var simulatedWorkTimeMs = WGLUUrl.getFloat('workTime', 0); |
| |
| // Don't produce frames at all. Simulates a broken app. |
| var noFrames = WGLUUrl.getBool('noFrames', false); |
| |
| // Use 1024x1024 per eye instead of the recommended base render |
| // resolution. |
| var standardSize = WGLUUrl.getBool('standardSize', false); |
| |
| // Multiply the recommended render resolution (or standardSize) in each |
| // direction. renderScale=2 means 4x the pixel count. |
| var renderScale = WGLUUrl.getFloat('renderScale', 1.0); |
| |
| // Support for automated latency testing. |
| var latencyPatch = WGLUUrl.getBool('latencyPatch', false); |
| var pureFlickerApp = WGLUUrl.getBool('pureFlickerApp', false); |
| |
| // Only submit every Nth presentation frame. The first frame is |
| // rendered, so a large value can be used to simulate a splash screen. |
| var submitInterval = WGLUUrl.getInt('submitInterval', 1); |
| |
| // Resize the canvas every N frames, toggling between full and half |
| // resolution. |
| var canvasResizeInterval = WGLUUrl.getInt('canvasResizeInterval', 0); |
| |
| // Draw only half the world cubes. Helps test variable render cost |
| // when combined with heavyGpu. |
| var halfOnly = WGLUUrl.getBool('halfOnly', false); |
| |
| // Automatically spin the world cubes. Intended for automated testing, |
| // not recommended for viewing in a headset. |
| var autorotate = WGLUUrl.getBool('autorotate', false); |
| |
| // Use antialiasing for the drawing canvas. Default is on, can disable |
| // for performance comparisons. |
| var antialias = WGLUUrl.getBool('antialias', true); |
| |
| var VELOCITY_SCALE = 0.25; |
| |
| var presentationFrameCounter = 0; |
| |
| // ================================ |
| // WebVR-specific code begins here. |
| // ================================ |
| |
| // WebGL setup. |
| var gl = null; |
| var cubeSea = null; |
| var stats = null; |
| |
| function onContextLost( event ) { |
| event.preventDefault(); |
| console.log( 'WebGL Context Lost.' ); |
| gl = null; |
| cubeSea = null; |
| stats = null; |
| } |
| |
| function onContextRestored( event ) { |
| console.log( 'WebGL Context Restored.' ); |
| initWebGL(vrDisplay ? vrDisplay.capabilities.hasExternalDisplay : false); |
| } |
| |
| var webglCanvas = document.getElementById("webgl-canvas"); |
| webglCanvas.addEventListener( 'webglcontextlost', onContextLost, false ); |
| webglCanvas.addEventListener( 'webglcontextrestored', onContextRestored, false ); |
| |
| function initWebGL (preserveDrawingBuffer) { |
| var glAttribs = { |
| alpha: false, |
| preserveDrawingBuffer: preserveDrawingBuffer, |
| antialias: antialias |
| }; |
| gl = webglCanvas.getContext("webgl", glAttribs); |
| if (!gl) { |
| gl = webglCanvas.getContext("experimental-webgl", glAttribs); |
| if (!gl) { |
| VRSamplesUtil.addError("Your browser does not support WebGL."); |
| return; |
| } |
| } |
| gl.clearColor(0.1, 0.2, 0.3, 1.0); |
| gl.enable(gl.DEPTH_TEST); |
| gl.enable(gl.CULL_FACE); |
| |
| var textureLoader = new WGLUTextureLoader(gl); |
| var texture = textureLoader.loadTexture("media/textures/cube-sea.png"); |
| cubeSea = new VRCubeSea(gl, texture, cubeCount, cubeScale, heavyGpu, |
| halfOnly, autorotate); |
| var enablePerformanceMonitoring = WGLUUrl.getBool( |
| 'enablePerformanceMonitoring', false); |
| stats = new WGLUStats(gl, enablePerformanceMonitoring); |
| |
| // Wait until we have a WebGL context to resize and start rendering. |
| window.addEventListener("resize", onResize.bind(this, 1.0), false); |
| onResize(); |
| window.requestAnimationFrame(onAnimationFrame); |
| } |
| |
| function onVRRequestPresent () { |
| vrDisplay.requestPresent([{ source: webglCanvas }]).then(function () { |
| }, function (err) { |
| var errMsg = "requestPresent failed."; |
| if (err && err.message) { |
| errMsg += "<br/>" + err.message |
| } |
| VRSamplesUtil.addError(errMsg, 2000); |
| }); |
| } |
| |
| function onVRExitPresent () { |
| if (!vrDisplay.isPresenting) |
| return; |
| |
| vrDisplay.exitPresent().then(function () { |
| }, function () { |
| VRSamplesUtil.addError("exitPresent failed.", 2000); |
| }); |
| } |
| |
| function onVRPresentChange () { |
| onResize(); |
| |
| if (vrDisplay.isPresenting) { |
| if (vrDisplay.capabilities.hasExternalDisplay) { |
| VRSamplesUtil.removeButton(vrPresentButton); |
| vrPresentButton = VRSamplesUtil.addButton("Exit VR", "E", "media/icons/cardboard64.png", onVRExitPresent); |
| } |
| } else { |
| if (vrDisplay.capabilities.hasExternalDisplay) { |
| VRSamplesUtil.removeButton(vrPresentButton); |
| vrPresentButton = VRSamplesUtil.addButton("Enter VR", "E", "media/icons/cardboard64.png", onVRRequestPresent); |
| } |
| } |
| } |
| |
| if (navigator.getVRDisplays) { |
| frameData = new VRFrameData(); |
| |
| navigator.getVRDisplays().then(function (displays) { |
| if (displays.length > 0) { |
| vrDisplay = displays[displays.length - 1]; |
| vrDisplay.depthNear = 0.1; |
| vrDisplay.depthFar = 1024.0; |
| |
| if (vrDisplay.capabilities.canPresent) |
| vrPresentButton = VRSamplesUtil.addButton("Enter VR", "E", "media/icons/cardboard64.png", onVRRequestPresent); |
| |
| // For the benefit of automated testing. Safe to ignore. |
| if (vrDisplay.capabilities.canPresent && WGLUUrl.getBool('canvasClickPresents', false)) |
| webglCanvas.addEventListener("click", onVRRequestPresent, false); |
| |
| window.addEventListener('vrdisplaypresentchange', onVRPresentChange, false); |
| window.addEventListener('vrdisplayactivate', onVRRequestPresent, false); |
| window.addEventListener('vrdisplaydeactivate', onVRExitPresent, false); |
| |
| initWebGL(vrDisplay.capabilities.hasExternalDisplay); |
| } else { |
| initWebGL(false); |
| VRSamplesUtil.addInfo("WebVR supported, but no VRDisplays found.", 3000); |
| } |
| }, function () { |
| VRSamplesUtil.addError("Your browser does not support WebVR. See <a href='http://webvr.info'>webvr.info</a> for assistance."); |
| }); |
| } else if (navigator.getVRDevices) { |
| initWebGL(false); |
| VRSamplesUtil.addError("Your browser supports WebVR but not the latest version. See <a href='http://webvr.info'>webvr.info</a> for more info."); |
| } else { |
| initWebGL(false); |
| VRSamplesUtil.addError("Your browser does not support WebVR. See <a href='http://webvr.info'>webvr.info</a> for assistance."); |
| } |
| |
| function onResize (opt_multiplier) { |
| if (vrDisplay && vrDisplay.isPresenting) { |
| var multiplier = opt_multiplier || 1; |
| var leftEye = vrDisplay.getEyeParameters("left"); |
| var rightEye = vrDisplay.getEyeParameters("right"); |
| |
| // If user has specified renderWidth/Height in URL, use that instead of the |
| // recommended resolution. Useful for testing to get cross-device comparable |
| // results. This still gets multiplied by renderScale if that is set. |
| var renderWidth = standardSize ? 1024 : Math.max(leftEye.renderWidth, rightEye.renderWidth); |
| var renderHeight = standardSize ? 1024 : Math.max(leftEye.renderHeight, rightEye.renderHeight); |
| |
| webglCanvas.width = renderWidth * 2 * renderScale * multiplier; |
| webglCanvas.height = renderHeight * renderScale * multiplier; |
| } else { |
| webglCanvas.width = webglCanvas.offsetWidth * window.devicePixelRatio; |
| webglCanvas.height = webglCanvas.offsetHeight * window.devicePixelRatio; |
| } |
| console.log('onResize', webglCanvas.width, webglCanvas.height); |
| } |
| |
| var now = (window.performance && performance.now) ? performance.now.bind(performance) : Date.now; |
| |
| function onAnimationFrame (t) { |
| // do not attempt to render if there is no available WebGL context |
| if (!gl || !stats || !cubeSea) { |
| return; |
| } |
| |
| stats.begin(); |
| |
| // Simulate a poorly designed app that doesn't provide frames. |
| if(noFrames) { |
| return; |
| } |
| |
| if (simulatedWorkTimeMs > 0) { |
| // Simulate a heavy Javascript workload |
| var start = now(); |
| mat4.perspective(projectionMat, Math.PI*0.4, webglCanvas.width / webglCanvas.height, 0.1, 1024.0); |
| while (now() - start < simulatedWorkTimeMs) { |
| // Spin our wheels doing useless BS :) |
| mat4.invert(projectionMat, projectionMat); |
| mat4.translate(projectionMat, projectionMat, [1.0, 1.0, 1.0]); |
| } |
| } |
| |
| gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); |
| |
| if (vrDisplay) { |
| vrDisplay.requestAnimationFrame(onAnimationFrame); |
| |
| vrDisplay.getFrameData(frameData); |
| |
| if (vrDisplay.isPresenting) { |
| var frameNum = presentationFrameCounter; |
| // Only count frames in presenting mode, to ensure the first |
| // presenting frame has frameNum=0. |
| ++presentationFrameCounter; |
| |
| if (pureFlickerApp) { |
| // Disable normal rendering and only act as a flicker app |
| // Useful on slow devices where rendering the cube sea and latency |
| // patch together has a significant effect on performance |
| let angularVelocity = vec3.len(frameData.pose.angularVelocity); |
| let brightness = Math.min(angularVelocity * VELOCITY_SCALE, 1.0); |
| gl.clearColor(brightness, brightness, brightness, 1.0); |
| gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); |
| vrDisplay.submitFrame(); |
| return; |
| } |
| |
| // Simulate an app that's only submitting on a subset of animation |
| // frames. This seems to be legal according to the WebVR spec? |
| // frameNum=0 is submitted, so setting submitInterval to a large |
| // number can simulate a splash screen. |
| if (submitInterval && frameNum % submitInterval > 0) { |
| return; |
| } |
| |
| // Test resizing canvas while presenting. This doesn't need to be |
| // very efficient, but should work. |
| if (canvasResizeInterval && frameNum % canvasResizeInterval == 0) { |
| // Toggle canvas between half size and normal size. |
| if (Math.floor(frameNum / canvasResizeInterval) % 2) { |
| onResize(0.5); |
| } else { |
| onResize(); |
| } |
| } |
| |
| gl.viewport(0, 0, webglCanvas.width * 0.5, webglCanvas.height); |
| cubeSea.render(frameData.leftProjectionMatrix, frameData.leftViewMatrix, stats, t); |
| |
| gl.viewport(webglCanvas.width * 0.5, 0, webglCanvas.width * 0.5, webglCanvas.height); |
| cubeSea.render(frameData.rightProjectionMatrix, frameData.rightViewMatrix, stats, t); |
| |
| if (latencyPatch) { |
| // Draw a square overlay in the right eye with brightness |
| // proportional to the magnitude of the headset's angular |
| // velocity. This is intended for a hardware latency tester. |
| let angularVelocity = vec3.len(frameData.pose.angularVelocity); |
| let brightness = Math.min(angularVelocity * VELOCITY_SCALE, 1.0); |
| gl.scissor(webglCanvas.width * 0.625, webglCanvas.height * 0.25, |
| webglCanvas.width * 0.25, webglCanvas.height * 0.5); |
| gl.enable(gl.SCISSOR_TEST); |
| gl.clearColor(brightness, brightness, brightness, 1.0); |
| gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); |
| gl.disable(gl.SCISSOR_TEST); |
| gl.clearColor(0.1, 0.2, 0.3, 1.0); |
| } |
| |
| vrDisplay.submitFrame(); |
| } else { |
| gl.viewport(0, 0, webglCanvas.width, webglCanvas.height); |
| mat4.perspective(projectionMat, Math.PI*0.4, webglCanvas.width / webglCanvas.height, 0.1, 1024.0); |
| cubeSea.render(projectionMat, frameData.leftViewMatrix, stats, t); |
| stats.renderOrtho(); |
| } |
| } else { |
| window.requestAnimationFrame(onAnimationFrame); |
| |
| // No VRDisplay found. |
| gl.viewport(0, 0, webglCanvas.width, webglCanvas.height); |
| mat4.perspective(projectionMat, Math.PI*0.4, webglCanvas.width / webglCanvas.height, 0.1, 1024.0); |
| mat4.identity(viewMat); |
| cubeSea.render(projectionMat, viewMat, stats, t); |
| |
| stats.renderOrtho(); |
| } |
| |
| stats.end(); |
| } |
| })(); |
| </script> |
| </body> |
| </html> |