Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
<!DOCTYPE html>
<html lang="en">
<head>
<title>GLSL Sandbox</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<style>
body {
background-color: #000000;
margin: 0;
padding: 0;
overflow: hidden;
}
button, select, a, a:visited {
padding: 8px 12px 8px 12px;
border: none;
border-radius: 5px;
margin-right: 5px;
color: #ffffff;
background-color: #000000;
opacity: 0.5;
font-family: Monospace;
font-size: 12px;
font-weight: bold;
cursor: pointer;
text-decoration: none;
}
button:hover, select:hover, a:hover {
opacity: 1;
box-shadow: 0 0 4px #FFF;
}
option {
color: #ffffff;
background-color: #000000;
}
</style>
</head>
<body>
<link rel="stylesheet" href="css/codemirror.css">
<link rel="stylesheet" href="css/default.css">
<script src="js/lzma.js"></script>
<script src='js/jquery.js'></script>
<script src='js/helpers.js'></script>
<script src="js/codemirror.js"></script>
<script src="js/glsl.js"></script>
<script id="example" type="x-shader/x-fragment">#ifdef GL_ES
precision mediump float;
#endif
uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
void main( void ) {
vec2 position = ( gl_FragCoord.xy / resolution.xy ) + mouse / 4.0;
float color = 0.0;
color += sin( position.x * cos( time / 15.0 ) * 80.0 ) + cos( position.y * cos( time / 15.0 ) * 10.0 );
color += sin( position.y * sin( time / 10.0 ) * 40.0 ) + cos( position.x * sin( time / 25.0 ) * 40.0 );
color += sin( position.x * sin( time / 5.0 ) * 10.0 ) + sin( position.y * sin( time / 35.0 ) * 80.0 );
color *= sin( time / 10.0 ) * 0.5;
gl_FragColor = vec4( vec3( color, color * 0.5, sin( color + time / 3.0 ) * 0.75 ), 1.0 );
}</script>
<script id="fragmentShader" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 resolution;
uniform sampler2D texture;
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
gl_FragColor = texture2D( texture, uv );
}
</script>
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec3 position;
void main() {
gl_Position = vec4( position, 1.0 );
}
</script>
<script id="surfaceVertexShader" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec2 surfacePosAttrib;
varying vec2 surfacePosition;
void main() {
surfacePosition = surfacePosAttrib;
gl_Position = vec4( position, 1.0 );
}
</script>
<script>
initialize_helper();
var compressor=initialize_compressor();
if ( !window.requestAnimationFrame ) {
window.requestAnimationFrame = ( function() {
return window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function ( callback, element ) {
window.setTimeout( callback, 1000 / 60 );
};
} )();
}
// Get older browsers safely through init code, so users can read the
// message about how to download newer browsers.
if (!Date.now) {
Date.now = function() {
return +new Date();
};
}
// Greetings to Iq/RGBA! ;)
var quality = 2, quality_levels = [ 0.5, 1, 2, 4, 8 ];
var toolbar, compileButton, fullscreenButton, compileTimer, errorLines = [];
var code, canvas, gl, buffer, currentProgram, vertexPosition, screenVertexPosition, panButton,
parameters = { startTime: Date.now(), time: 0, mouseX: 0.5, mouseY: 0.5, screenWidth: 0, screenHeight: 0 },
surface = { centerX: 0, centerY: 0, width: 1, height: 1, isPanning: false, isZooming: false, lastX: 0, lastY: 0 },
frontTarget, backTarget, screenProgram, getWebGL, resizer = {}, compileOnChangeCode = true;
init();
if (gl) { animate(); }
function init() {
if (!document.addEventListener) {
document.location = 'http://get.webgl.org/';
return;
}
canvas = document.createElement( 'canvas' );
canvas.style.display = 'block';
document.body.appendChild( canvas );
//
//
toolbar = document.createElement( 'div' );
toolbar.style.position = 'absolute';
toolbar.style.top = '25px';
toolbar.style.left = '25px';
document.body.appendChild( toolbar );
var rightside = document.createElement( 'div' );
rightside.style.cssFloat = 'right';
toolbar.appendChild( rightside );
panButton = document.createElement( 'button' );
panButton.textContent = 'pan/zoom';
panButton.style.cursor = 'move';
panButton.style.display = 'none';
panButton.title = "Pan: left-drag, Zoom: right-drag. Use 'hide code' for a large pan/zoom area.";
rightside.appendChild( panButton );
fullscreenButton = document.createElement( 'button' );
fullscreenButton.textContent = 'fullscreen';
fullscreenButton.title = 'Press F11 to enter or leave fullscreen mode';
fullscreenButton.addEventListener( 'click', function ( event ) {
if (document.body.requestFullScreen) {
document.body.requestFullScreen();
} else if (document.body.mozRequestFullScreen) {
document.body.mozRequestFullScreen();
} else if (document.body.webkitRequestFullScreen) {
document.body.webkitRequestFullScreen( Element.ALLOW_KEYBOARD_INPUT );
}
}, false );
rightside.appendChild( fullscreenButton );
var button = document.createElement( 'a' );
button.textContent = 'gallery';
button.href = 'http://glsl.heroku.com/';
rightside.appendChild( button );
var button = document.createElement( 'button' );
button.textContent = 'hide code';
button.addEventListener( 'click', function ( event ) {
if ( isCodeVisible() ) {
button.textContent = 'show code';
code.getWrapperElement().style.display = 'none';
compileButton.style.visibility = 'hidden';
set_save_button('hidden');
set_parent_button('hidden');
stopHideUI();
} else {
button.textContent = 'hide code';
code.getWrapperElement().style.display = '';
compileButton.style.visibility = 'visible';
set_save_button('visible');
set_parent_button('visible');
}
}, false );
toolbar.appendChild( button );
var select = document.createElement( 'select' );
for ( var i = 0; i < quality_levels.length; i ++ ) {
var option = document.createElement( 'option' );
option.textContent = quality_levels[ i ];
if ( quality_levels[ i ] == quality ) option.selected = true;
select.appendChild( option );
}
select.addEventListener( 'change', function ( event ) {
quality = quality_levels[ event.target.selectedIndex ];
onWindowResize();
}, false );
toolbar.appendChild( select );
compileButton = document.createElement( 'button' );
compileButton.textContent = 'compile';
compileButton.addEventListener( 'click', function ( event ) {
compile();
}, false );
toolbar.appendChild( compileButton );
// from helper.js
add_server_buttons();
// Initialise WebGL
try {
gl = canvas.getContext( 'experimental-webgl', { preserveDrawingBuffer: true } );
} catch( error ) { }
if ( !gl ) {
alert("WebGL not supported, but code will be shown.");
} else {
// Create vertex buffer (2 triangles)
buffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( [ - 1.0, - 1.0, 1.0, - 1.0, - 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, - 1.0, 1.0 ] ), gl.STATIC_DRAW );
// Create surface buffer (coordinates at screen corners)
surface.buffer = gl.createBuffer();
}
// initialize code editor
code = CodeMirror(document.body, {
lineNumbers: true,
matchBrackets: true,
indentWithTabs: true,
tabSize: 8,
indentUnit: 8,
mode: "text/x-glsl",
onChange: function () {
if (compileOnChangeCode) {
clearTimeout(compileTimer);
compileTimer = setTimeout(compile, 500);
}
}
});
code.getWrapperElement().style.display = '';
resizer.offsetMouseX = 0;
resizer.offsetMouseY = 0;
resizer.isResizing = false;
resizer.currentWidth = 100;
resizer.currentHeight = 100;
resizer.minWidth = 100;
resizer.minHeight = 100;
resizer.maxWidth = 100;
resizer.maxHeight = 100;
resizer.element = document.createElement( 'div' );
resizer.element.className = 'resizer';
code.getWrapperElement().appendChild(resizer.element);
resizer.element.addEventListener( 'mousedown', function ( event ) {
if (event.button !== 2) {
resizer.offsetMouseX = event.clientX - resizer.currentWidth;
resizer.offsetMouseY = event.clientY - resizer.currentHeight;
resizer.isResizing = true;
event.preventDefault();
}
}, false );
if (gl) {
var surfaceMouseDown = function ( event ) {
if (event.shiftKey) {
resetSurface();
}
if (event.button === 0) {
surface.isPanning = true;
document.body.style.cursor = 'move';
} else {
surface.isZooming = true;
document.body.style.cursor = 'se-resize';
panButton.style.cursor = 'se-resize';
}
surface.lastX = event.clientX;
surface.lastY = event.clientY;
event.preventDefault();
};
var noContextMenu = function ( event ) {
event.preventDefault();
};
canvas.addEventListener( 'mousedown', surfaceMouseDown, false );
panButton.addEventListener( 'mousedown', surfaceMouseDown, false );
canvas.addEventListener( 'contextmenu', noContextMenu, false);
panButton.addEventListener( 'contextmenu', noContextMenu, false);
}
var clientXLast, clientYLast;
document.addEventListener( 'mousemove', function ( event ) {
var clientX = event.clientX;
var clientY = event.clientY;
if (clientXLast == clientX && clientYLast == clientY)
return;
clientXLast = clientX;
clientYLast = clientY;
stopHideUI();
var codeElement, dx, dy;
parameters.mouseX = clientX / window.innerWidth;
parameters.mouseY = 1 - clientY / window.innerHeight;
if (resizer.isResizing) {
resizer.currentWidth = Math.max(Math.min(clientX - resizer.offsetMouseX, resizer.maxWidth), resizer.minWidth);
resizer.currentHeight = Math.max(Math.min(clientY - resizer.offsetMouseY, resizer.maxHeight), resizer.minWidth);
codeElement = code.getWrapperElement();
codeElement.style.width = resizer.currentWidth + 'px';
codeElement.style.height = resizer.currentHeight + 'px';
code.refresh();
event.preventDefault();
} else if (surface.isPanning) {
dx = clientX - surface.lastX;
dy = clientY - surface.lastY;
surface.centerX -= dx * surface.width / window.innerWidth;
surface.centerY += dy * surface.height / window.innerHeight;
surface.lastX = clientX;
surface.lastY = clientY;
computeSurfaceCorners();
event.preventDefault();
} else if (surface.isZooming) {
dx = clientX - surface.lastX;
dy = clientY - surface.lastY;
surface.height *= Math.pow(0.997, dx + dy);
surface.lastX = clientX;
surface.lastY = clientY;
computeSurfaceCorners();
event.preventDefault();
}
}, false );
function settleDown ( event ) {
resizer.isResizing = surface.isPanning = surface.isZooming = false;
document.body.style.cursor = 'default';
panButton.style.cursor = 'move';
}
function mouseLeave(event) {
settleDown(event);
if (!isCodeVisible())
startHideUITimer();
}
document.addEventListener( 'mouseup', settleDown, false );
document.addEventListener( 'mouseleave', mouseLeave, false );
onWindowResize();
window.addEventListener( 'resize', onWindowResize, false );
load_url_code();
compileScreenProgram();
}
function isCodeVisible() {
return code && code.getWrapperElement().style.display !== 'none';
}
var hideUITimer;
var isUIHidden = false;
function startHideUITimer () {
stopHideUITimer();
if (!isUIHidden && !isCodeVisible())
hideUITimer = window.setTimeout(onHideUITimer, 1000 * 5 );
function onHideUITimer() {
stopHideUITimer();
if (!isUIHidden && !isCodeVisible()) {
isUIHidden = true;
toolbar.style.display = 'none';
document.body.style.cursor = 'none';
}
}
function stopHideUITimer () {
if (hideUITimer) {
window.clearTimeout(hideUITimer);
hideUITimer = 0;
}
}
}
function stopHideUI () {
if (isUIHidden) {
isUIHidden = false;
toolbar.style.display = '';
document.body.style.cursor = '';
}
startHideUITimer();
}
function computeSurfaceCorners() {
if (gl) {
surface.width = surface.height * parameters.screenWidth / parameters.screenHeight;
var halfWidth = surface.width * 0.5, halfHeight = surface.height * 0.5;
gl.bindBuffer( gl.ARRAY_BUFFER, surface.buffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( [
surface.centerX - halfWidth, surface.centerY - halfHeight,
surface.centerX + halfWidth, surface.centerY - halfHeight,
surface.centerX - halfWidth, surface.centerY + halfHeight,
surface.centerX + halfWidth, surface.centerY - halfHeight,
surface.centerX + halfWidth, surface.centerY + halfHeight,
surface.centerX - halfWidth, surface.centerY + halfHeight ] ), gl.STATIC_DRAW );
}
}
function resetSurface() {
surface.centerX = surface.centerY = 0;
surface.height = 1;
computeSurfaceCorners();
}
function compile() {
if (!gl) {
if (!getWebGL) {
getWebGL = true;
compileButton.addEventListener( 'click', function ( event ) {
document.location = 'http://get.webgl.org/';
}, false );
compileButton.title = 'http://get.webgl.org/';
compileButton.style.color = '#ff0000';
compileButton.textContent = 'WebGL not supported!';
set_save_button('hidden');
}
return;
}
var program = gl.createProgram();
var fragment = code.getValue();
var vertex = document.getElementById( 'surfaceVertexShader' ).textContent;
var vs = createShader( vertex, gl.VERTEX_SHADER );
var fs = createShader( fragment, gl.FRAGMENT_SHADER );
if ( vs == null || fs == null ) return null;
gl.attachShader( program, vs );
gl.attachShader( program, fs );
gl.deleteShader( vs );
gl.deleteShader( fs );
gl.linkProgram( program );
if ( !gl.getProgramParameter( program, gl.LINK_STATUS ) ) {
var error = gl.getProgramInfoLog( program );
compileButton.title = error;
console.error( error );
console.error( 'VALIDATE_STATUS: ' + gl.getProgramParameter( program, gl.VALIDATE_STATUS ), 'ERROR: ' + gl.getError() );
compileButton.style.color = '#ff0000';
compileButton.textContent = 'compiled with errors';
set_save_button('hidden');
return;
}
if ( currentProgram ) {
gl.deleteProgram( currentProgram );
setURL( fragment );
}
currentProgram = program;
compileButton.style.color = '#00ff00';
compileButton.textContent = 'compiled successfully';
set_save_button('visible');
panButton.style.display = (fragment.indexOf('varying vec2 surfacePosition;') >= 0) ? 'inline' : 'none';
// Cache uniforms
cacheUniformLocation( program, 'time' );
cacheUniformLocation( program, 'mouse' );
cacheUniformLocation( program, 'resolution' );
cacheUniformLocation( program, 'backbuffer' );
cacheUniformLocation( program, 'surfaceSize' );
// Load program into GPU
gl.useProgram( currentProgram );
// Set up buffers
surface.positionAttribute = gl.getAttribLocation(currentProgram, "surfacePosAttrib");
gl.enableVertexAttribArray(surface.positionAttribute);
vertexPosition = gl.getAttribLocation(currentProgram, "position");
gl.enableVertexAttribArray( vertexPosition );
}
function compileScreenProgram() {
if (!gl) { return; }
var program = gl.createProgram();
var fragment = document.getElementById( 'fragmentShader' ).textContent;
var vertex = document.getElementById( 'vertexShader' ).textContent;
var vs = createShader( vertex, gl.VERTEX_SHADER );
var fs = createShader( fragment, gl.FRAGMENT_SHADER );
gl.attachShader( program, vs );
gl.attachShader( program, fs );
gl.deleteShader( vs );
gl.deleteShader( fs );
gl.linkProgram( program );
if ( !gl.getProgramParameter( program, gl.LINK_STATUS ) ) {
console.error( 'VALIDATE_STATUS: ' + gl.getProgramParameter( program, gl.VALIDATE_STATUS ), 'ERROR: ' + gl.getError() );
return;
}
screenProgram = program;
gl.useProgram( screenProgram );
cacheUniformLocation( program, 'resolution' );
cacheUniformLocation( program, 'texture' );
screenVertexPosition = gl.getAttribLocation(screenProgram, "position");
gl.enableVertexAttribArray( screenVertexPosition );
}
function cacheUniformLocation( program, label ) {
if ( program.uniformsCache === undefined ) {
program.uniformsCache = {};
}
program.uniformsCache[ label ] = gl.getUniformLocation( program, label );
}
//
function createTarget( width, height ) {
var target = {};
target.framebuffer = gl.createFramebuffer();
target.renderbuffer = gl.createRenderbuffer();
target.texture = gl.createTexture();
// set up framebuffer
gl.bindTexture( gl.TEXTURE_2D, target.texture );
gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
gl.bindFramebuffer( gl.FRAMEBUFFER, target.framebuffer );
gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, target.texture, 0 );
// set up renderbuffer
gl.bindRenderbuffer( gl.RENDERBUFFER, target.renderbuffer );
gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height );
gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, target.renderbuffer );
// clean up
gl.bindTexture( gl.TEXTURE_2D, null );
gl.bindRenderbuffer( gl.RENDERBUFFER, null );
gl.bindFramebuffer( gl.FRAMEBUFFER, null);
return target;
}
function createRenderTargets() {
frontTarget = createTarget( parameters.screenWidth, parameters.screenHeight );
backTarget = createTarget( parameters.screenWidth, parameters.screenHeight );
}
//
var dummyFunction = function() {};
//
function htmlEncode(str){
return String(str)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
//
function createShader( src, type ) {
var shader = gl.createShader( type );
var line, lineNum, lineError, index = 0, indexEnd;
while (errorLines.length > 0) {
line = errorLines.pop();
code.setLineClass(line, null);
code.clearMarker(line);
}
gl.shaderSource( shader, src );
gl.compileShader( shader );
compileButton.title = '';
if ( !gl.getShaderParameter( shader, gl.COMPILE_STATUS ) ) {
var error = gl.getShaderInfoLog( shader );
// Remove trailing linefeed, for FireFox's benefit.
while ((error.length > 1) && (error.charCodeAt(error.length - 1) < 32)) {
error = error.substring(0, error.length - 1);
}
compileButton.title = error;
console.error( error );
compileButton.style.color = '#ff0000';
compileButton.textContent = 'compiled with errors';
set_save_button('hidden');
while (index >= 0) {
index = error.indexOf("ERROR: 0:", index);
if (index < 0) { break; }
index += 9;
indexEnd = error.indexOf(':', index);
if (indexEnd > index) {
lineNum = parseInt(error.substring(index, indexEnd));
if ((!isNaN(lineNum)) && (lineNum > 0)) {
index = indexEnd + 1;
indexEnd = error.indexOf("ERROR: 0:", index);
lineError = htmlEncode((indexEnd > index) ? error.substring(index, indexEnd) : error.substring(index));
line = code.setMarker(lineNum - 1, '<abbr title="' + lineError + '">' + lineNum + '</abbr>', "errorMarker");
code.setLineClass(line, "errorLine");
errorLines.push(line);
}
}
}
return null;
}
return shader;
}
//
function onWindowResize( event ) {
var isMaxWidth = ((resizer.currentWidth === resizer.maxWidth) || (resizer.currentWidth === resizer.minWidth)),
isMaxHeight = ((resizer.currentHeight === resizer.maxHeight) || (resizer.currentHeight === resizer.minHeight));
toolbar.style.width = window.innerWidth - 47 + 'px';
resizer.isResizing = false;
resizer.maxWidth = window.innerWidth - 75;
resizer.maxHeight = window.innerHeight - 125;
if (isMaxWidth || (resizer.currentWidth > resizer.maxWidth)) {
resizer.currentWidth = resizer.maxWidth;
}
if (isMaxHeight || (resizer.currentHeight > resizer.maxHeight)) {
resizer.currentHeight = resizer.maxHeight;
}
if (resizer.currentWidth < resizer.minWidth) { resizer.currentWidth = resizer.minWidth; }
if (resizer.currentHeight < resizer.minHeight) { resizer.currentHeight = resizer.minHeight; }
code.getWrapperElement().style.top = '75px';
code.getWrapperElement().style.left = '25px';
code.getWrapperElement().style.width = resizer.currentWidth + 'px';
code.getWrapperElement().style.height = resizer.currentHeight + 'px';
canvas.width = window.innerWidth / quality;
canvas.height = window.innerHeight / quality;
canvas.style.width = window.innerWidth + 'px';
canvas.style.height = window.innerHeight + 'px';
parameters.screenWidth = canvas.width;
parameters.screenHeight = canvas.height;
computeSurfaceCorners();
if (gl) {
gl.viewport( 0, 0, canvas.width, canvas.height );
createRenderTargets();
}
}
//
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
if ( !currentProgram ) return;
parameters.time = Date.now() - parameters.startTime;
// Set uniforms for custom shader
gl.useProgram( currentProgram );
gl.uniform1f( currentProgram.uniformsCache[ 'time' ], parameters.time / 1000 );
gl.uniform2f( currentProgram.uniformsCache[ 'mouse' ], parameters.mouseX, parameters.mouseY );
gl.uniform2f( currentProgram.uniformsCache[ 'resolution' ], parameters.screenWidth, parameters.screenHeight );
gl.uniform1i( currentProgram.uniformsCache[ 'backbuffer' ], 0 );
gl.uniform2f( currentProgram.uniformsCache[ 'surfaceSize' ], surface.width, surface.height );
gl.bindBuffer( gl.ARRAY_BUFFER, surface.buffer );
gl.vertexAttribPointer( surface.positionAttribute, 2, gl.FLOAT, false, 0, 0 );
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.vertexAttribPointer( vertexPosition, 2, gl.FLOAT, false, 0, 0 );
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, backTarget.texture );
// Render custom shader to front buffer
gl.bindFramebuffer( gl.FRAMEBUFFER, frontTarget.framebuffer );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
gl.drawArrays( gl.TRIANGLES, 0, 6 );
// Set uniforms for screen shader
gl.useProgram( screenProgram );
gl.uniform2f( screenProgram.uniformsCache[ 'resolution' ], parameters.screenWidth, parameters.screenHeight );
gl.uniform1i( screenProgram.uniformsCache[ 'texture' ], 1 );
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.vertexAttribPointer( screenVertexPosition, 2, gl.FLOAT, false, 0, 0 );
gl.activeTexture( gl.TEXTURE1 );
gl.bindTexture( gl.TEXTURE_2D, frontTarget.texture );
// Render front buffer to screen
gl.bindFramebuffer( gl.FRAMEBUFFER, null );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
gl.drawArrays( gl.TRIANGLES, 0, 6 );
// Swap buffers
var tmp = frontTarget;
frontTarget = backTarget;
backTarget = tmp;
}
</script>
</body>
</html>