UNPKG

toloframework

Version:

Javascript/HTML/CSS compiler for Firefox OS or nodewebkit apps using modules in the nodejs style.

324 lines (292 loc) 9.27 kB
"use strict"; /** * Creating a WebGL program for shaders is painful. This class * simplifies the process. * * @class Program * * Object properties starting with `$` are WebGL uniforms or attributes. * Uniforms behave as expected: you can read/write a value. * Attributes when read, return the location. And when written, enable/disabled * this attribute. So you read integers and writte booleans. * * @param gl - WebGL context. * @param codes - Object with two mandatory attributes: `vert` for * vertex shader and `frag` for fragment shader. * @param includes - (optional) If defined, the `#include foo` * directives of shaders will be replaced by the content of * `includes.foo`. */ function Program( gl, codes, includes ) { if ( typeof codes.vert !== 'string' ) { throw Error( '[webgl.program] Missing attribute `vert` in argument `codes`!' ); } if ( typeof codes.frag !== 'string' ) { throw Error( '[webgl.program] Missing attribute `frag` in argument `codes`!' ); } codes = parseIncludes( codes, includes ); this.gl = gl; Object.freeze( this.gl ); this._typesNamesLookup = getTypesNamesLookup( gl ); var shaderProgram = gl.createProgram(); var shaderVert = getVertexShader( gl, codes.vert ); var shaderFrag = getFragmentShader( gl, codes.frag ); gl.attachShader( shaderProgram, shaderVert ); gl.attachShader( shaderProgram, shaderFrag ); gl.linkProgram( shaderProgram ); gl.detachShader( shaderProgram, shaderVert ); gl.deleteShader( shaderVert ); gl.detachShader( shaderProgram, shaderFrag ); gl.deleteShader( shaderFrag ); this.program = shaderProgram; Object.freeze( this.program ); this.use = function () { gl.useProgram( shaderProgram ); }; this.use(); createAttributes( this, gl, shaderProgram ); createUniforms( this, gl, shaderProgram ); } Program.prototype.getTypeName = function ( typeId ) { return this._typesNamesLookup[ typeId ]; }; var BPE = ( new Float32Array() ).BYTES_PER_ELEMENT; Program.prototype.bindAttribs = function ( buffer ) { var gl = this.gl; gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); var names = Array.prototype.slice.call( arguments, 1 ); var totalSize = 0; names.forEach( function ( name ) { var attrib = this.attribs[ name ]; if ( !attrib ) { throw "Cannot find attribute \"" + name + "\"!\n" + "It may be not active because unused in the shader.\n" + "Available attributes are: " + Object.keys( this.attribs ).map( function ( name ) { return '"' + name + '"'; } ).join( ", " ); } totalSize += ( attrib.size * attrib.length ) * BPE; }, this ); var offset = 0; names.forEach( function ( name ) { var attrib = this.attribs[ name ]; gl.enableVertexAttribArray( attrib.location ); gl.vertexAttribPointer( attrib.location, attrib.size * attrib.length, gl.FLOAT, false, // No normalisation. totalSize, offset ); offset += ( attrib.size * attrib.length ) * BPE; }, this ); }; module.exports = Program; function createAttributes( that, gl, shaderProgram ) { var index, item; var attribs = {}; var attribsCount = gl.getProgramParameter( shaderProgram, gl.ACTIVE_ATTRIBUTES ); for ( index = 0; index < attribsCount; index++ ) { item = gl.getActiveAttrib( shaderProgram, index ); item.typeName = that.getTypeName( item.type ); item.length = getSize( gl, item ); item.location = gl.getAttribLocation( shaderProgram, item.name ); console.info( "item=", item ); attribs[ item.name ] = item; } that.attribs = attribs; Object.freeze( that.attribs ); } function createAttributeSetter( gl, item, shaderProgram ) { console.info( "item=", item ); var name = item.name; return function ( v ) { if ( typeof v !== 'boolean' ) { throw "[webgl.program::$" + name + "] Value must be a boolean: true if you want to enable this attribute, and false to disable it."; } if ( v ) { console.log( "enableVertexAttribArray(", gl.getAttribLocation( shaderProgram, name ), ")" ); gl.enableVertexAttribArray( gl.getAttribLocation( shaderProgram, name ) ); } else { gl.disableVertexAttribArray( gl.getAttribLocation( shaderProgram, name ) ); } }; } function createAttributeGetter( gl, item, shaderProgram ) { var loc = gl.getAttribLocation( shaderProgram, item.name ); return function () { return loc; }; } function createUniforms( that, gl, shaderProgram ) { var index, item; var uniforms = {}; var uniformsCount = gl.getProgramParameter( shaderProgram, gl.ACTIVE_UNIFORMS ); for ( index = 0; index < uniformsCount; index++ ) { item = gl.getActiveUniform( shaderProgram, index ); uniforms[ item.name ] = gl.getUniformLocation( shaderProgram, item.name ); Object.defineProperty( that, '$' + item.name, { set: createUniformSetter( gl, item, uniforms[ item.name ], that._typesNamesLookup ), get: createUniformGetter( item ), enumerable: true, configurable: false } ); } that.uniforms = uniforms; Object.freeze( that.uniforms ); } /** * This is a preprocessor for shaders. * Directives `#include` will be replaced by the content of the * correspondent attribute in `includes`. */ function parseIncludes( codes, includes ) { var result = {}; var id, code; for ( id in codes ) { code = codes[ id ]; result[ id ] = code.split( '\n' ).map( function ( line ) { if ( line.trim().substr( 0, 8 ) != '#include' ) return line; var pos = line.indexOf( '#include' ) + 8; var includeName = line.substr( pos ).trim(); // We accept all this systaxes: // #include foo // #include 'foo' // #include <foo> // #include "foo" if ( "'<\"".indexOf( includeName.charAt( 0 ) ) > -1 ) { includeName = includeName.substr( 1, includeName.length - 2 ); } var snippet = includes[ includeName ]; if ( typeof snippet !== 'string' ) { console.error( "Include <" + includeName + "> not found in ", includes ); throw Error( "Include not found in shader: " + includeName ); } return snippet; } ).join( "\n" ); } return result; } function createUniformSetter( gl, item, nameGL, lookup ) { var nameJS = '_$' + item.name; switch ( item.type ) { case gl.BYTE: case gl.UNSIGNED_BYTE: case gl.SHORT: case gl.UNSIGNED_SHORT: case gl.INT: case gl.UNSIGNED_INT: case gl.SAMPLER_2D: // For textures, we specify the texture unit. if ( item.size == 1 ) { return function ( v ) { gl.uniform1i( nameGL, v ); this[ nameJS ] = v; }; } else { return function ( v ) { gl.uniform1iv( nameGL, v ); this[ nameJS ] = v; }; } break; case gl.FLOAT: if ( item.size == 1 ) { return function ( v ) { gl.uniform1f( nameGL, v ); this[ nameJS ] = v; }; } else { return function ( v ) { gl.uniform1fv( nameGL, v ); this[ nameJS ] = v; }; } break; case gl.FLOAT_MAT3: if ( item.size == 1 ) { return function ( v ) { gl.uniformMatrix3fv( nameGL, false, v ); this[ nameJS ] = v; }; } else { throw Error( "[webgl.program.createWriter] Don't know how to deal arrays of FLOAT_MAT3 in uniform `" + item.name + "'!'" ); } break; case gl.FLOAT_MAT4: if ( item.size == 1 ) { return function ( v ) { gl.uniformMatrix4fv( nameGL, false, v ); this[ nameJS ] = v; }; } else { throw Error( "[webgl.program.createWriter] Don't know how to deal arrays of FLOAT_MAT4 in uniform `" + item.name + "'!'" ); } break; default: throw Error( "[webgl.program.createWriter] Don't know how to deal with uniform `" + item.name + "` of type " + lookup[ item.type ] + "!" ); } } function createUniformGetter( item ) { var name = '_$' + item.name; return function () { return this[ name ]; }; } function getShader( type, gl, code ) { var shader = gl.createShader( type ); gl.shaderSource( shader, code ); gl.compileShader( shader ); if ( !gl.getShaderParameter( shader, gl.COMPILE_STATUS ) ) { console.log( code ); console.error( "An error occurred compiling the shader: " + gl.getShaderInfoLog( shader ) ); return null; } return shader; } function getFragmentShader( gl, code ) { return getShader( gl.FRAGMENT_SHADER, gl, code ); } function getVertexShader( gl, code ) { return getShader( gl.VERTEX_SHADER, gl, code ); } function getTypesNamesLookup( gl ) { var lookup = {}; var k, v; for ( k in gl ) { v = gl[ k ]; if ( typeof v === 'number' ) { lookup[ v ] = k; } } return lookup; } function getSize( gl, item ) { switch ( item.type ) { case gl.FLOAT_VEC4: return 4; case gl.FLOAT_VEC3: return 3; case gl.FLOAT_VEC2: return 2; case gl.FLOAT: return 1; default: throw "[webgl.program:getSize] I don't know the size of the attribute '" + item.name + "' because I don't know the type " + getTypeName( item.type ) + "!"; } }