/**
* Program constructor. Create gl program and shaders. You can pass optional shader code to immediatly compile shaders
* @param {WebGLRenderingContext} gl webgl context this program belongs to
* @param {String} [vert=undefined] an optional vertex shader code. See {@link Program#compile}
* @param {String} [frag=undefined] an optional fragment shader code See {@link Program#compile}
* @param {String} [defs=undefined] an optional string prepend to both fragment and vertex shader code. See {@link Program#compile}.
* @see {@link Program#compile}
*
* @example <caption>For the given vertex shader</caption>
* attribute vec3 aPosition;
* uniform mat4 uMVP;
* uniform vec3 uCameraPosition;
*
* @example <caption>access to uniforms and attributes</caption>
* var prg = new Program( gl, vert, frag );
* prg.use()
*
* var mvp = glmatrix.mat4.create()
* prg.uMVP( mvp )
*
* prg.uCameraPosition( 0, 0, 0 )
* // or
* var campos = glmatrix.vec3.create()
* prg.uCameraPosition( campos )
* // or
* prg.uCameraPosition( [0, 1, 2] )
*
* // get the uniform location
* var matLocation = prg.uMVP()
* // or attribute location
* var aPosition = prg.aPosition()
*
*
* @class
* @classdesc Program class provide shader compilation and linking functionality.
* It also give you convenient access to active uniforms and attributes.
* Once compiled, the Program object list all used uniforms/attributes and provide getter/setter function for each one. See {@link Program} constructor.
*
*/
function Program( gl, vert, frag, defs ){
this.gl = gl;
this.program = gl.createProgram();
this.vShader = gl.createShader( gl.VERTEX_SHADER );
this.fShader = gl.createShader( gl.FRAGMENT_SHADER );
gl.attachShader(this.program, this.vShader);
gl.attachShader(this.program, this.fShader);
if( vert !== undefined && frag !== undefined ){
this.compile( vert, frag, defs );
}
}
/**
* Program.verbose
* can be set to false to prevent shader code logs on glsl errors (default to true)
*/
Program.verbose = true;
Program.prototype = {
/**
* Shortcut for gl.useProgram()
* alias program.bind()
*/
use : function(){
this.gl.useProgram( this.program );
},
/**
* Compile vertex and fragment shader then link gl program
* This method can be safely called several times.
* @param {String} vert vertex shader code
* @param {String} frag fragment shader code
* @param {String} [prefix=''] an optional string append to both fragment and vertex code
*/
compile : function( vert, frag, prefix ){
prefix = ( prefix || '' ) + '\n';
var gl = this.gl;
if( !( compileShader( gl, this.fShader, prefix + frag ) &&
compileShader( gl, this.vShader, prefix + vert ) ) ) {
return false;
}
gl.linkProgram(this.program);
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
Program.warn(gl.getProgramInfoLog(this.program));
return false;
}
this._grabParameters();
return true;
},
/**
* Delete program and shaders
*/
dispose : function() {
if( this.gl !== null ){
this.gl.deleteProgram( this.program );
this.gl.deleteShader( this.fShader );
this.gl.deleteShader( this.vShader );
this.gl = null;
}
},
/*
* List all uniforms and attributes and create helper function on Program instance
* eg :
* for a uniform vec3 uDirection;
* create a method
* program.uDirection( 1, 0, 0 );
*/
_grabParameters : function(){
var gl = this.gl,
prg = this.program;
// Uniforms
// ========
var numUniforms = gl.getProgramParameter( prg, gl.ACTIVE_UNIFORMS );
var context = {
texIndex : 0
};
for ( var uniformIndex = 0; uniformIndex < numUniforms; ++uniformIndex )
{
var uniform = gl.getActiveUniform( prg, uniformIndex );
// safari 8.0 issue,
// when recompiling shader and link the progam again, old uniforms are kept in ACTIVE_UNIFORMS count but return null here
if( uniform === null ){
gl.getError(); // also flush error
continue;
}
var uName = uniform.name,
n = uName.indexOf('[');
if( n >= 0 ){
uName = uName.substring(0, n);
}
var uLocation = gl.getUniformLocation( prg, uniform.name );
this[uName] = getUniformSetter( uniform.type, uLocation, gl, context );
}
// Attributes
// ==========
var numAttribs = gl.getProgramParameter( prg, gl.ACTIVE_ATTRIBUTES );
for (var aIndex = 0; aIndex < numAttribs; ++aIndex )
{
var attribName = gl.getActiveAttrib( prg, aIndex ).name;
var aLocation = gl.getAttribLocation( prg, attribName );
this[attribName] = getAttribAccess( aLocation );
}
}
};
/**
* alias to Program.use()
*/
Program.prototype.bind = Program.prototype.use;
/*
* internal logs
*/
Program.warn = function(str){
if( Program.verbose ){
console.warn(str);
}
};
// -------------------------------------------------
// UTILITIES
// -------------------------------------------------
/*
* Shader logging utilities
*/
var __pads = ['',' ',' ',' ',''];
function appendLine( l, i ){
return __pads[String(i+1).length] + ( i+1 ) + ': ' + l;
}
/*
* Format shader code
* add padded lines number
*/
function formatCode( shader ) {
return shader.split( '\n' ).map( appendLine ).join( '\n' );
}
var USetFMap = {};
USetFMap[ String(5126 ) /*FLOAT */ ] = '1f';
USetFMap[ String(35664) /*FLOAT_VEC2 */ ] = '2f';
USetFMap[ String(35665) /*FLOAT_VEC3 */ ] = '3f';
USetFMap[ String(35666) /*FLOAT_VEC4 */ ] = '4f';
USetFMap[ String(35667) /*INT_VEC2 */ ] = '2i';
USetFMap[ String(35668) /*INT_VEC3 */ ] = '3i';
USetFMap[ String(35669) /*INT_VEC4 */ ] = '4i';
USetFMap[ String(35670) /*BOOL */ ] = '1i';
USetFMap[ String(35671) /*BOOL_VEC2 */ ] = '2i';
USetFMap[ String(35672) /*BOOL_VEC3 */ ] = '3i';
USetFMap[ String(35673) /*BOOL_VEC4 */ ] = '4i';
USetFMap[ String(35674) /*FLOAT_MAT2 */ ] = 'Matrix2f';
USetFMap[ String(35675) /*FLOAT_MAT3 */ ] = 'Matrix3f';
USetFMap[ String(35676) /*FLOAT_MAT4 */ ] = 'Matrix4f';
USetFMap[ String(5124 ) /*INT */ ] = '1i';
USetFMap[ String(35678) /*SAMPLER_2D */ ] = '1i';
USetFMap[ String(35680) /*SAMPLER_CUBE*/ ] = '1i';
/*
* Uniform upload utilities
*/
function getUniformSetFunctionName( type ){
type = String(type);
return 'uniform' + USetFMap[type];
}
/*
* For a given uniform's type, return the proper setter function
*/
function getUniformSetter( type, location, gl, context ){
switch( type ){
case gl.FLOAT_MAT2 :
case gl.FLOAT_MAT3 :
case gl.FLOAT_MAT4 :
return getMatrixSetFunction( type, location, gl, context );
case gl.SAMPLER_2D :
case gl.SAMPLER_CUBE:
return getSamplerSetFunction( type, location, gl, context );
default :
return getUniformSetFunction( type, location, gl, context );
}
}
/*
* setter factory for vector uniforms
* return a function wich take both array or arguments
*/
function getUniformSetFunction( type, location, gl, context ){
context;
var fname = getUniformSetFunctionName( type );
return function(){
if( arguments.length === 1 && arguments[0].length !== undefined ){
gl[fname+'v']( location, arguments[0] );
} else if( arguments.length > 0) {
gl[fname].apply( gl, Array.prototype.concat.apply( location, arguments) );
}
return location;
};
}
/*
* setter factory for matrix uniforms
*/
function getMatrixSetFunction( type, location, gl, context ){
context;
var fname = getUniformSetFunctionName( type );
return function(){
if( arguments.length > 0 && arguments[0].length !== undefined ){
gl[fname+'v']( location, !!arguments[1], arguments[0] );
}
return location;
};
}
/*
* setter factory for sampler uniforms
*/
function getSamplerSetFunction( type, location, gl, context ){
var unit = context.texIndex++;
return function(){
if( arguments.length === 1 ) {
if( arguments[0].bind !== undefined ){ // is texture
arguments[0].bind( unit );
gl.uniform1i( location, unit );
} else {
gl.uniform1i( location, arguments[0] );
}
}
return location;
};
}
/*
* getter factory for attributes
*/
function getAttribAccess( attrib ){
return function(){
return attrib;
};
}
/*
* Shader compilation utility
*/
function compileShader( gl, shader, code ){
gl.shaderSource( shader, code );
gl.compileShader( shader );
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
Program.warn( gl.getShaderInfoLog(shader) );
Program.warn( formatCode( code ) );
return false;
}
return true;
}
module.exports = Program;