935 lines
24 KiB
HTML
Executable File
935 lines
24 KiB
HTML
Executable File
<!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">precision mediump float;
|
|
|
|
#extension GL_OES_standard_derivatives : enable
|
|
|
|
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">
|
|
|
|
precision mediump float;
|
|
|
|
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 = '/';
|
|
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 {
|
|
|
|
// enable dFdx, dFdy, fwidth
|
|
gl.getExtension('OES_standard_derivatives');
|
|
|
|
// 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, '&')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>');
|
|
|
|
}
|
|
|
|
//
|
|
|
|
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>
|