blob: 86530501fcf0415c459a32cc2c06b4f2fd833ca6 [file]
<!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>09 - Splash Screen</title>
<!--
This sample demonstrates best practices for displaying splash screens
while loading large content.
-->
<style>
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 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)) {
var polyfill = new WebVRPolyfill({
// Ensures the polyfill is always active on mobile, due to providing
// a polyfilled CardboardVRDisplay when no native API is available,
// and also polyfilling even when the native API is available, due to
// providing a CardboardVRDisplay when no native VRDisplays exist.
PROVIDE_MOBILE_VRDISPLAY: true,
// Polyfill optimizations
DIRTY_SUBMIT_FRAME_BINDINGS: true,
BUFFER_SCALE: 0.75,
});
}
</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>
<script src="js/vr-splash.js"></script>
</head>
<body>
<p>This sample only displays in VR. To view click the "Enter VR" button.</p>
<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;
// ================================
// WebVR-specific code begins here.
// ================================
// For many WebVR apps it can be handy to display a splash screen while
// the main scene's resources load, especially if the VR content isn't
// preloaded via a magic window. This lets the user know that their
// request to enter VR has been acknowledged and provides a super minimal
// tracked environment while everything else is being built.
// There are some best practices to follow when doing a splash screen,
// which this sample aims to demonstrate. They are:
// * Render a single frame and allow the underlying VR system to reproject
// it for you.
// * Always render the splash such against a black background so the user
// can't tell where the edge of the reprojected surface is.
// * The splash screen image should fit entirely in the users field of
// view, otherwise reprojection may show clipped edges.
// * Always render the splash directly in front of the user, no matter
// where they are looking at the moment the splash screen is shown.
// * Avoid the temptation to continuously render a spinner or other small
// animation. You're just eating up GPU time that should be used to load
// faster, and you're likely to jank terribly.
// * Beyond that, avoid even sparsely updated progress bars, as the user
// may not be looking at the splash wen you update and then reprojection
// breaks.
// * If you MUST update the splash while loading, always draw the updated
// frames directy in front of the user. This is really janky, though, so
// please just don't?
// * In general render the ABSOLUTE MINIMUM NECESSARY and spend all the
// rest of your time loading. People did not come to your site to gawk
// at your fabulous splash image.
// WebGL setup.
var webglCanvas = null;
var gl = null;
var cubeSea = null;
var stats = null;
var textureLoader = null;
var splashScreen = null;
function onContextLost( event ) {
event.preventDefault();
console.log( 'WebGL Context Lost.' );
gl = null;
cubeSea = null;
stats = null;
textureLoader = null;
splashScreen = null;
}
function onContextRestored( event ) {
console.log( 'WebGL Context Restored.' );
initWebGL(vrDisplay ? vrDisplay.capabilities.hasExternalDisplay : false);
}
webglCanvas = document.createElement('canvas');
webglCanvas.addEventListener( 'webglcontextlost', onContextLost, false );
webglCanvas.addEventListener( 'webglcontextrestored', onContextRestored, false );
// This sample is going to use a slightly different start up sequence.
// In order to show the splash screen effect we won't have any magic
// window content and only begin loading the scene once the "Enter VR"
// button is clicked. At that point we'll render a single frame of the
// splash screen and let it reproject while the real scene loads. Once
// that scene is ready we'll begin rendering it.
function initWebGL (preserveDrawingBuffer) {
var glAttribs = {
alpha: false,
preserveDrawingBuffer: preserveDrawingBuffer
};
var useWebgl2 = WGLUUrl.getBool('webgl2', false);
var contextTypes = useWebgl2 ? ["webgl2"] : ["webgl", "experimental-webgl"];
for (var i in contextTypes) {
gl = webglCanvas.getContext(contextTypes[i], glAttribs);
if (gl)
break;
}
if (!gl) {
var webglType = (useWebgl2 ? "WebGL 2" : "WebGL")
VRSamplesUtil.addError("Your browser does not support " + webglType + ".");
return;
}
gl.clearColor(0.1, 0.2, 0.3, 1.0);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
textureLoader = new WGLUTextureLoader(gl);
document.body.appendChild(webglCanvas);
// Wait until we have a WebGL context to resize and start rendering.
window.addEventListener("resize", onResize, false);
onResize();
}
function loadSplash() {
textureLoader.loadTexture("media/textures/splash.png", null, function(texture) {
splashScreen = new VRSplashScreen(gl, texture, false);
// When the splash texture is loaded, draw a single frame with it.
vrDisplay.requestAnimationFrame(onSplashFrame);
});
}
function loadScene() {
// Totally cheating here to pretend like our load times are long
setTimeout(function () {
var texture = textureLoader.loadTexture("media/textures/cube-sea.png");
cubeSea = new VRCubeSea(gl, texture);
var enablePerformanceMonitoring = WGLUUrl.getBool(
'enablePerformanceMonitoring', false);
stats = new WGLUStats(gl, enablePerformanceMonitoring);
// Once the scene is fully loaded kick off the animation loop
vrDisplay.requestAnimationFrame(onAnimationFrame);
}, 5000);
}
function onVRRequestPresent () {
// Only use preserveDrawingBuffer if we have an external display to
// mirror to.
initWebGL(vrDisplay.capabilities.hasExternalDisplay);
vrDisplay.requestPresent([{ source: webglCanvas }]).then(function () {
// Immediate upon presentation load up the splash image.
loadSplash();
}, 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 {
// For this sample we'll tear down the GL scene when we exit VR.
document.body.removeChild(webglCanvas);
webglCanvas = null;
gl = null;
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);
} else {
VRSamplesUtil.addInfo("WebVR supported, but no VRDisplays found.", 3000);
}
});
} else if (navigator.getVRDevices) {
VRSamplesUtil.addError("Your browser supports WebVR but not the latest version. See <a href='http://webvr.info'>webvr.info</a> for more info.");
} else {
VRSamplesUtil.addError("Your browser does not support WebVR. See <a href='http://webvr.info'>webvr.info</a> for assistance.");
}
function onResize () {
if (vrDisplay && vrDisplay.isPresenting) {
// If we're presenting we want to use the drawing buffer size
// recommended by the VRDevice, since that will ensure the best
// results post-distortion.
var leftEye = vrDisplay.getEyeParameters("left");
var rightEye = vrDisplay.getEyeParameters("right");
// For simplicity we're going to render both eyes at the same size,
// even if one eye needs less resolution. You can render each eye at
// the exact size it needs, but you'll need to adjust the viewports to
// account for that.
webglCanvas.width = Math.max(leftEye.renderWidth, rightEye.renderWidth) * 2;
webglCanvas.height = Math.max(leftEye.renderHeight, rightEye.renderHeight);
}
}
function onSplashFrame (t) {
// do not attempt to render if there is no available WebGL context
if (!gl) {
return;
}
// Splash screens should always be cleared to black to avoid showing the
// edges of the reprojected frame
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Note: We explicity do NOT request a new animation frame here. That
// should wait until the content is loaded and we are ready to begin
// rendering the full scene.
vrDisplay.getFrameData(frameData);
// The splash screen should always be rendered directly in front of the
// user no matter what direction they are looking. This ensures that the
// full splash can be seen and reprojected. Otherwise the splash screen
// might be partially off-screen when rendered and subsequent
// reprojections will be cut off at the edges.
// Splash screen images can be mono, but it's fun to use a stereo image
// too! That's what we're doing here.
gl.viewport(0, 0, webglCanvas.width * 0.5, webglCanvas.height);
splashScreen.render(frameData.leftProjectionMatrix, "left");
gl.viewport(webglCanvas.width * 0.5, 0, webglCanvas.width * 0.5, webglCanvas.height);
splashScreen.render(frameData.rightProjectionMatrix, "right");
vrDisplay.submitFrame();
// Now that the splash screen has been rendered we can begin loading the
// real scene:
loadScene();
}
function onAnimationFrame (t) {
// do not attempt to render if there is no available WebGL context
if (!gl || !stats || !cubeSea) {
window.requestAnimationFrame(onAnimationFrame);
return;
}
stats.begin();
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
vrDisplay.requestAnimationFrame(onAnimationFrame);
vrDisplay.getFrameData(frameData);
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);
vrDisplay.submitFrame();
stats.end();
}
})();
</script>
</body>
</html>