/**
* Author: thegoldenmule
* Date: 2/2/13
* Time: 8:26 PM
*/
(function(global) {
"use strict";
/**
* @desc Defines potential types of uniforms.
* @static
* @private
* @type {{FLOAT: number, VEC2: number, VEC3: number, VEC4: number, MAT2: number, MAT3: number, MAT4: number}}
*/
var ShaderUniformTypes = {
FLOAT : 0,
VEC2 : 1,
VEC3 : 2,
VEC4 : 3,
MAT2 : 4,
MAT3 : 5,
MAT4 : 6
};
/**
* @class Shader
* @desc Defines an object that represents a glsl shader. This object
* provides an interface through which uniforms may be uploaded and shader
* definitions may be compiled.
*
* @returns {Shader}
* @author thegoldenmule
* @constructor
*/
global.Shader = function() {
var that = this;
/**
* @member global.Shader#vertexBufferAttributePointer
* @desc This pointer is updated when the shader is compiled and points
* to the index of the vertex buffer.
* @type {number}
*/
that.vertexBufferAttributePointer = null;
/**
* @member global.Shader#vertexColorBufferAttributePointer
* @desc This pointer is updated when the shader is compiled and points
* to the index of the vertex color buffer.
* @type {number}
*/
that.vertexColorBufferAttributePointer = null;
/**
* @member global.Shader#uvBufferAttributePointer
* @desc This pointer is updated when the shader is compiled and points
* to the index of the vertex uv buffer.
* @type {number}
*/
that.uvBufferAttributePointer = null;
/**
* @member global.Shader#projectionMatrixUniformPointer
* @desc This pointer is updated when the shader is compiled and points
* to the index of the projection matrix uniform.
* @type {number}
*/
that.projectionMatrixUniformPointer = null;
/**
* @member global.Shader#modelMatrixUniformPointer
* @desc This pointer is updated when the shader is compiled and points
* to the index of the model matrix uniform.
* @type {number}
*/
that.modelMatrixUniformPointer = null;
/**
* @member global.Shader#colorUniformPointer
* @desc This pointer is updated when the shader is compiled and points
* to the index of the color uniform.
* @type {number}
*/
that.colorUniformPointer = null;
/**
* @member global.Shader#depthUniformPointer
* @desc This pointer is updated when the shader is compiled and points
* to the index of the depth uniform.
* @type {number}
*/
that.depthUniformPointer = null;
/**
* @member global.Shader#mainTextureUniformPointer
* @desc This pointer is updated when the shader is compiled and points
* to the index of the main texture uniform.
* @type {number}
*/
that.mainTextureUniformPointer = null;
/**
* @member global.Shader#secTextureUniformPointer
* @desc This pointer is updated when the shader is compiled and points
* to the index of the secondary texture uniform.
* @type {number}
*/
that.secTextureUniformPointer = null;
var _compiled = false,
_dirtyPointers = true,
_shaderProgram = null,
_vertexProgramId = "texture-shader-vs",
_fragmentProgramId = "texture-shader-fs",
_customUniforms = {};
/**
* @function global.Shader#setShaderProgramIds
* @desc This method sets the ids of the vertex and fragment shaders.
* Once these are changed, the shader needs to be recompiled.
* @param {String} vertex The id of the vertex shader.
* @param {String} fragment The id of the fragment shader.
*/
that.setShaderProgramIds = function(vertex, fragment) {
if (vertex === _vertexProgramId && fragment === _fragmentProgramId) {
return;
}
_vertexProgramId = vertex;
_fragmentProgramId = fragment;
_shaderProgram = null;
_compiled = false;
};
/**
* @function global.Shader#getShaderProgram
* @desc Once compiled, this function returns the shader program that
* should be uploaded to the GPU.
* @returns {GLShaderProgram}
*/
that.getShaderProgram = function() {
return _shaderProgram;
};
/**
* @function global.Shader#setUniformFloat
* @desc Sets a float value for a specific uniform.
* @param {String} name The name of the uniform to set.
* @param {Number} value The value to upload.
* @type {Function}
*/
that.setUniformFloat = setUniformValue(ShaderUniformTypes.FLOAT);
/**
* @function global.Shader#setUniformVec2
* @desc Sets a vec2 value for a specific uniform.
* @param {String} name The name of the uniform to set.
* @param {Float32Array} value The value to upload.
* @type {Function}
*/
that.setUniformVec2 = setUniformValue(ShaderUniformTypes.VEC2);
/**
* @function global.Shader#setUniformVec3
* @desc Sets a vec3 value for a specific uniform.
* @param {String} name The name of the uniform to set.
* @param {Float32Array} value The value to upload.
* @type {Function}
*/
that.setUniformVec3 = setUniformValue(ShaderUniformTypes.VEC3);
/**
* @function global.Shader#setUniformVec4
* @desc Sets a vec4 value for a specific uniform.
* @param {String} name The name of the uniform to set.
* @param {Float32Array} value The value to upload.
* @type {Function}
*/
that.setUniformVec4 = setUniformValue(ShaderUniformTypes.VEC4);
/**
* @function global.Shader#setUniformMat2
* @desc Sets a mat2 value for a specific uniform.
* @param {String} name The name of the uniform to set.
* @param {Float32Array} value The value to upload.
* @type {Function}
*/
that.setUniformMat2 = setUniformValue(ShaderUniformTypes.MAT2);
/**
* @function global.Shader#setUniformMat3
* @desc Sets a mat3 value for a specific uniform.
* @param {String} name The name of the uniform to set.
* @param {Float32Array} value The value to upload.
* @type {Function}
*/
that.setUniformMat3 = setUniformValue(ShaderUniformTypes.MAT3);
/**
* @function global.Shader#setUniformMat4
* @desc Sets a mat4 value for a specific uniform.
* @param {String} name The name of the uniform to set.
* @param {Float32Array} value The value to upload.
* @type {Function}
*/
that.setUniformMat4 = setUniformValue(ShaderUniformTypes.MAT4);
/**
* @function global.Shader#compile
* @desc Compiles a shader, given a WebGLContext. This is necessary if
* the context is lost or the shader ids have been changed.
* @param {WebGLContext} ctx The context to compile with.
* @returns {boolean} Returns true if successful.
*/
that.compile = function(ctx) {
if (!_compiled) {
if (null === _vertexProgramId || null === _fragmentProgramId) {
return false;
}
if (window.isTwoDeeDebug) {
Log.debug("Compiling program {" + _vertexProgramId + " :: " + _fragmentProgramId + "}");
}
var fragmentShader = compileShader(ctx, _fragmentProgramId);
var vertexShader = compileShader(ctx, _vertexProgramId);
_shaderProgram = ctx.createProgram();
if (null === vertexShader || null === fragmentShader) {
return false;
}
try {
ctx.attachShader(_shaderProgram, vertexShader);
ctx.attachShader(_shaderProgram, fragmentShader);
} catch (error) {
return false;
}
ctx.linkProgram(_shaderProgram);
if (!ctx.getProgramParameter(_shaderProgram, ctx.LINK_STATUS)) {
return false;
}
_compiled = true;
}
if (_dirtyPointers) {
setupPointers(ctx);
_dirtyPointers = true;
}
return true;
};
/**
* @function global.Shader#pushCustomUniforms
* @desc This method uploads all custom uniforms to the GPU.
* @param {WebGLContext} ctx The context to upload through.
*/
that.pushCustomUniforms = function(ctx) {
Object.keys(_customUniforms).forEach(
function(name) {
var definition = _customUniforms[name];
if (-1 === definition.pointer) {
return;
}
switch (definition.type) {
case ShaderUniformTypes.FLOAT:
ctx.uniform1f(definition.pointer, definition.value);
break;
case ShaderUniformTypes.VEC2:
ctx.uniform2fv(definition.pointer, definition.value);
break;
case ShaderUniformTypes.VEC3:
ctx.uniform3fv(definition.pointer, definition.value);
break;
case ShaderUniformTypes.VEC4:
ctx.uniform4fv(definition.pointer, definition.value);
break;
case ShaderUniformTypes.MAT2:
ctx.uniformMatrix2fv(definition.pointer, false, definition.value);
break;
case ShaderUniformTypes.MAT3:
ctx.uniformMatrix3fv(definition.pointer, false, definition.value);
break;
case ShaderUniformTypes.MAT4:
ctx.uniformMatrix4fv(definition.pointer, false, definition.value);
break;
}
});
};
/**
* @private
* @desc Returns a function that will set a uniform by type.
* @param {Number} type The type of uniform.
* @returns {Function}
*/
function setUniformValue(type) {
return function (name, value) {
if (undefined === _customUniforms[name]) {
_customUniforms[name] = {
type: type,
pointer: -1,
value: value
};
_dirtyPointers = true;
} else {
_customUniforms[name].value = value;
}
};
}
/**
* @function global.Shader#compuleShader
* @private
* @desc Heavily borrowed from:
* https://developer.mozilla.org/en-US/docs/WebGL/Adding_2D_content_to_a_WebGL_context
*
* This method compiles a shader by element id.
* @param {WebGLContext} ctx The context with which to compile
* @param {string} id The string id of the DOMElement containing the
* shader definition.
* @returns {GLShaderProgram}
*/
function compileShader(ctx, id) {
var script = document.getElementById(id);
if (null === script) {
Log.error("Could not find shader " + id + ".");
return null;
}
var source = "";
var currentChild = script.firstChild;
while (currentChild) {
if (currentChild.nodeType === currentChild.TEXT_NODE) {
source += currentChild.textContent;
}
currentChild = currentChild.nextSibling;
}
var shader = null;
if ("x-shader/x-fragment" === script.type) {
shader = ctx.createShader(ctx.FRAGMENT_SHADER);
} else if ("x-shader/x-vertex" === script.type) {
shader = ctx.createShader(ctx.VERTEX_SHADER);
} else {
// Unknown shader type
Log.error("Unknown shader type : " + script.type);
return null;
}
ctx.shaderSource(shader, source);
// Compile the shader program
ctx.compileShader(shader);
// See if it compiled successfully
if (!ctx.getShaderParameter(shader, ctx.COMPILE_STATUS)) {
if (window.isTwoDeeDebug) {
Log.error("Could not compile shader :\n{}", ctx.getShaderInfoLog(shader));
}
return null;
}
return shader;
}
/**
* @function global.Shader#setupPointers
* @desc Sets up all uniform + attribute pointers.
* @param {WebGLContext} ctx The context from which to grab pointers.
* @private
*/
function setupPointers(ctx) {
setupAttributePointers(ctx);
setupUniformPointers(ctx);
setupCustomUniformPointers(ctx);
}
/**
* @function global.Shader#setupAttributePointers
* @desc Sets up all attribute pointers.
* @param {WebGLContext} ctx The context from which to grab pointers.
* @private
*/
function setupAttributePointers(ctx) {
that.vertexBufferAttributePointer = ctx.getAttribLocation(_shaderProgram, "aPosition");
if (-1 !== that.vertexBufferAttributePointer) {
ctx.enableVertexAttribArray(that.vertexBufferAttributePointer);
}
that.vertexColorBufferAttributePointer = ctx.getAttribLocation(_shaderProgram, "aColor");
if (-1 !== that.vertexColorBufferAttributePointer) {
ctx.enableVertexAttribArray(that.vertexColorBufferAttributePointer);
}
that.uvBufferAttributePointer = ctx.getAttribLocation(_shaderProgram, "aUV");
if (-1 !== that.uvBufferAttributePointer) {
ctx.enableVertexAttribArray(that.uvBufferAttributePointer);
}
}
/**
* @function global.Shader#setupUniformPointers
* @desc Sets up all uniform pointers.
* @param {WebGLContext} ctx The context from which to grab pointers.
* @private
*/
function setupUniformPointers(ctx) {
that.projectionMatrixUniformPointer = ctx.getUniformLocation(_shaderProgram, "uProjectionMatrix");
that.modelMatrixUniformPointer = ctx.getUniformLocation(_shaderProgram, "uModelViewMatrix");
that.colorUniformPointer = ctx.getUniformLocation(_shaderProgram, "uColor");
that.depthUniformPointer = ctx.getUniformLocation(_shaderProgram, "uDepth");
that.mainTextureUniformPointer = ctx.getUniformLocation(_shaderProgram, "uMainTextureSampler");
that.secTextureUniformPointer = ctx.getUniformLocation(_shaderProgram, "uSecTextureSampler");
}
/**
* TODO: This is suboptimal.
*
* @function global.Shader#setupCustomUniformPointers
* @desc Sets up all custom uniform pointers.
* @param {WebGLContext} ctx The context from which to grab pointers.
* @private
*/
function setupCustomUniformPointers(ctx) {
Object.keys(_customUniforms).forEach(
function(name) {
var definition = _customUniforms[name];
definition.pointer = ctx.getUniformLocation(_shaderProgram, name);
});
}
return that;
};
global.Shader.prototype = {
constructor: global.Shader
};
})(this);