define(["kick/core/ProjectAsset", "kick/core/Constants", "./GLSLConstants", "kick/core/Util", "./UniformDescriptor", "kick/math/Vec3", "kick/math/Vec4", "kick/math/Mat4", "kick/math/Mat3", "kick/core/EngineSingleton", "kick/core/Observable"],
function (ProjectAsset, Constants, GLSLConstants, Util, UniformDescriptor, Vec3, Vec4, Mat4, Mat3, EngineSingleton, Observable) {
"use strict";
var Shader,
shaderUniqueVersionCounter = 0,
shaderUniqueNameCounter = 0,
vec3Zero = Vec3.create(),
tmpVec4 = Vec4.create(),
tempMat4 = Mat4.create(),
tempMat3 = Mat3.create(),
ASSERT = Constants._ASSERT,
isMaterialUniformName = function (name) {return name.charAt(0) !== "_"; };
/**
* GLSL Shader object<br>
* The shader basically encapsulates a GLSL shader programs, but makes sure that the correct
* WebGL settings are set when the shader is bound (such as if blending is enabled or not).<br>
* The Shader extend the default WebGL GLSL in the following way:
* <ul>
* <li>
* <code>#pragma include <filename></code> includes of the following KickJS file as a string:
* <ul>
* <li>light.glsl</li>
* <li>shadowmap.glsl</li>
* </ul>
* </li>
* <li>Auto binds the following uniform variables:
* <ul>
* <li><code>_mvProj</code> (mat4) Model view projection matrix</li>
* <li><code>_m</code> (mat4) Model matrix</li>
* <li><code>_mv</code> (mat4) Model view matrix</li>
* <li><code>_v</code> (mat4) View matrix</li>
* <li><code>_worldCamPos</code> (vec4) Camera position in world coordinate</li>
* <li><code>_world2object</code> (mat4) World to Object coordinate transformation</li>
* <li><code>_norm</code> (mat3) Normal matrix (the inverse transpose of the upper 3x3 model view matrix - needed when scaling is scaling is non-uniform)</li>
* <li><code>_time</code> (float) Run time of engine</li>
* <li><code>_ambient</code> (vec3) Ambient light</li>
* <li><code>_viewport</code> (vec2) Viewport size</li>
* <li><code>_dLight</code> (mat3) Directional light matrix. </li>
* <li><code>_dLight[0]</code> (vec3) Directional light direction in eye coordinates.</li>
* <li><code>_dLight[1]</code> (vec3) Directional light color intensity</li>
* <li><code>_dLight[2]</code> (vec3) Directional light half vector</li>
* <li><code>_dLightWorldDir</code> (vec3) Directional light world direction</li>
* <li><code>_pLights[n]</code> (mat3) Point light matrix</li>
* <li><code>_pLights[n][0]</code> (mat3) Point light id n position</li>
* <li><code>_pLights[n][1]</code> (mat3) Point light id n color intensity</li>
* <li><code>_pLights[n][2]</code> (mat3) Point light id n attenuation vector [const, linear, quadratic]</li>
*
* </ul>
* </li>
* <li>Defines <code>SHADOW</code> (Boolean) and <code>LIGHTS</code> (Integer) based on the current configuration of the engine (cannot be modified runtime). </li>
* </ul>
* @example
* var diffuseShader = project.load(project.ENGINE_SHADER_DIFFUSE);
* @example
* var vertexShaderStr = "attribute vec3 vertex;\n"+
* "uniform mat4 _mvProj;\n"+
* "void main(void) {\n"+
* " gl_Position = _mvProj * vec4(vertex, 1.0);\n"+
* "}";
* var fragmentShaderStr = "uniform highp float _time;\n"+
* "void main(void) {\n"+
* " highp float fraction = mod(_time/1000.0,1.0);\n"+
* " gl_FragColor = vec4(fraction,fraction,fraction, 1.0);\n"+
* "}";
* var shader = new kick.material.Shader( {
* vertexShaderSrc: vertexShaderStr,
* fragmentShaderSrc: fragmentShaderStr
* });
* @class Shader
* @namespace kick.material
* @constructor
* @param {Object} config
* @extends kick.core.ProjectAsset
*/
Shader = function (config) {
// extend ProjectAsset
ProjectAsset(this, config, "kick.material.Shader");
if (ASSERT){
if (config === EngineSingleton.engine){
Util.fail("Shader constructor changed - engine parameter is removed");
}
}
var engine = EngineSingleton.engine,
gl = engine.gl,
glState = engine.glState,
thisObj = this,
_shaderProgramId = -1,
_depthMask = true,
_faceCulling = Constants.GL_BACK,
_zTest = Constants.GL_LESS,
_blend = false,
_blendSFactorRGB = Constants.GL_SRC_ALPHA,
_blendDFactorRGB = Constants.GL_ONE_MINUS_SRC_ALPHA,
_blendSFactorAlpha = Constants.GL_SRC_ALPHA,
_blendDFactorAlpha = Constants.GL_ONE_MINUS_SRC_ALPHA,
_polygonOffsetEnabled = false,
_polygonOffsetFactor = 2.5,
_polygonOffsetUnits = 10.0,
_renderOrder = 1000,
_dataURI = "memory://void",
_name = "",
blendKey,
_activeUniforms = [],
_engineUniforms = [],
_materialUniforms = [],
_lookupUniform = {},
glslConstants = GLSLConstants,
_vertexShaderSrc = glslConstants["__error_vs.glsl"],
_fragmentShaderSrc = glslConstants["__error_fs.glsl"],
_defaultUniforms,
_errorLog = Util.fail,
uniqueVersionCounter = -1,
/**
* Updates the blend key that identifies blend+blendSFactor+blendDFactor<br>
* The key is used to fast determine if the blend settings needs to be updated
* @method updateBlendKey
* @private
*/
updateBlendKey = function () {
blendKey = (_blendSFactorRGB + (_blendDFactorRGB << 10) + (_blendSFactorAlpha << 20) + (_blendDFactorAlpha << 30)) * (_blend ? -1 : 1);
},
/**
* Invoke shader compilation
* @method compileShader
* @param {String} str
* @param {Boolean} isFragmentShader
* @private
*/
compileShader = function (str, isFragmentShader) {
var shader,
infoLog,
c = Constants;
str = Shader.getPrecompiledSource(str);
if (isFragmentShader) {
shader = gl.createShader(c.GL_FRAGMENT_SHADER);
} else {
shader = gl.createShader(c.GL_VERTEX_SHADER);
}
gl.shaderSource(shader, str);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, c.GL_COMPILE_STATUS)) {
infoLog = gl.getShaderInfoLog(shader);
if (typeof _errorLog === "function") {
_errorLog(infoLog);
}
if (isFragmentShader) {
thisObj.fireEvent('fragmentShaderError', infoLog);
} else {
thisObj.fireEvent('vertexShaderError', infoLog);
}
return null;
}
return shader;
},
updateCullFace = function () {
var currentFaceCulling = glState.faceCulling;
if (currentFaceCulling !== _faceCulling) {
if (_faceCulling === Constants.GL_NONE) {
gl.disable(Constants.GL_CULL_FACE);
} else {
if (!currentFaceCulling || currentFaceCulling === Constants.GL_NONE) {
gl.enable(Constants.GL_CULL_FACE);
}
gl.cullFace(_faceCulling);
}
glState.faceCulling = _faceCulling;
}
},
updateDepthProperties = function () {
if (glState.zTest !== _zTest) {
gl.depthFunc(_zTest);
glState.zTest = _zTest;
}
if (glState.depthMaskCache !== _depthMask) {
gl.depthMask(_depthMask);
glState.depthMaskCache = _depthMask;
}
},
updateBlending = function () {
if (glState.blendKey !== blendKey) {
glState.blendKey = blendKey;
if (_blend) {
gl.enable(Constants.GL_BLEND);
gl.blendFuncSeparate(_blendSFactorRGB, _blendDFactorRGB,_blendSFactorAlpha, _blendDFactorAlpha);
} else {
gl.disable(Constants.GL_BLEND);
}
}
},
updatePolygonOffset = function () {
if (glState.polygonOffsetEnabled !== _polygonOffsetEnabled) {
glState.polygonOffsetEnabled = _polygonOffsetEnabled;
if (_polygonOffsetEnabled) {
gl.enable(Constants.GL_POLYGON_OFFSET_FILL);
} else {
gl.disable(Constants.GL_POLYGON_OFFSET_FILL);
}
}
if (_polygonOffsetEnabled) {
gl.polygonOffset(_polygonOffsetFactor, _polygonOffsetUnits);
}
},
getDefaultUniform = function (type, size) {
switch (type) {
case Constants.GL_FLOAT:
return new Float32Array(size);
case Constants.GL_FLOAT_MAT2:
return new Float32Array(4 * size);
case Constants.GL_FLOAT_MAT3:
return new Float32Array(9 * size);
case Constants.GL_FLOAT_MAT4:
return new Float32Array(16 * size);
case Constants.GL_FLOAT_VEC2:
return new Float32Array(2 * size);
case Constants.GL_FLOAT_VEC3:
return new Float32Array(3 * size);
case Constants.GL_FLOAT_VEC4:
return new Float32Array(4 * size);
case Constants.GL_INT:
return new Int32Array(size);
case Constants.GL_INT_VEC2:
return new Int32Array(2 * size);
case Constants.GL_INT_VEC3:
return new Int32Array(3 * size);
case Constants.GL_INT_VEC4:
return new Int32Array(4 * size);
case Constants.GL_SAMPLER_CUBE:
if (size !== 1) {
Util.fail("Texture arrays not currently supported");
}
return engine.project.load(engine.project.ENGINE_TEXTURE_CUBEMAP_WHITE);
case Constants.GL_SAMPLER_2D:
if (size !== 1) {
Util.fail("Texture arrays not currently supported");
}
return engine.project.load(engine.project.ENGINE_TEXTURE_WHITE);
default:
Util.fail("Unknown type");
}
},
updateActiveUniforms = function (numberOfActiveUniforms) {
var uniform,
uniformDescriptor,
uniformLocation,
oldDefaultUniforms = _defaultUniforms,
i,
defaultValue;
_activeUniforms = [];
_lookupUniform = {};
_defaultUniforms = {};
_materialUniforms.length = 0;
_engineUniforms.length = 0;
for (i = 0; i < numberOfActiveUniforms; i++) {
uniform = gl.getActiveUniform(_shaderProgramId, i);
uniformLocation = gl.getUniformLocation(_shaderProgramId, uniform.name);
if (Constants._DEBUG) {
uniformLocation.shader = thisObj;
uniformLocation.shaderVersion = uniqueVersionCounter;
}
uniformDescriptor = new UniformDescriptor(uniform.name, uniform.type, uniform.size, uniformLocation);
Object.freeze(uniformDescriptor);
_activeUniforms[i] = uniformDescriptor;
_lookupUniform[uniform.name] = uniformDescriptor;
if (isMaterialUniformName(uniform.name)) {
defaultValue = getDefaultUniform(uniform.type, uniform.size);
_defaultUniforms[uniform.name] = defaultValue;
_materialUniforms.push(uniformDescriptor);
} else {
_engineUniforms.push(uniformDescriptor);
}
}
// restore the old default uniforms (if any)
if (oldDefaultUniforms) {
thisObj.defaultUniforms = oldDefaultUniforms;
}
};
Observable.call(this, [
/**
* Fired when shader is updated
* @event shaderUpdated
* @param {kick.material.Shader} shaderInstance
*/
"shaderUpdated",
/**
* Fired when vertex shader is unable to compile
* @event vertexShaderError
* @param {string} errorMessage
*/
"vertexShaderError",
/**
* Fired when fragment shader is unable to compile
* @event fragmentShaderError
* @param {string} errorMessage
*/
"fragmentShaderError",
/**
* Fired when shader is unable to link
* @event linkerError
* @param {string} errorMessage
*/
"linkerError"
]
);
/**
* Registers a listener to the shader.
* @method addListener
* @param {Function} listenerFn a function called when shader is updated
* @deprecated Use addEventListener('shaderUpdated', listenerFn) instead
*/
this.addListener = function (listenerFn) {
Util.fail("Use addEventListener('shaderUpdated', listenerFn) instead");
if (ASSERT) {
if (typeof listenerFn !== "function") {
Util.warn("Shader.addListener: listenerFn not function");
}
}
thisObj.addEventListener("shaderUpdated", listenerFn);
};
/**
* Removes a listener to the shader.
* @method removeListener
* @param {Function} listenerFn a function called when shader is updated
* @deprecated Use removeEventListener('shaderUpdated', listenerFn) instead
*/
this.removeListener = function (listenerFn) {
Util.fail("Use addEventListener('shaderUpdated', listenerFn) instead");
if (ASSERT) {
if (typeof listenerFn !== "function") {
Util.warn("Shader.removeListener: listenerFn not function");
}
}
thisObj.removeEventListener("shaderUpdated", listenerFn);
};
/**
* @method contextLost
* @protected
*/
this.contextLost = function () {
gl = null;
_shaderProgramId = -1;
_activeUniforms.length = 0;
_engineUniforms.length = 0;
_materialUniforms.length = 0;
};
/**
* This method is public and may be called multiple times (both from materials using the shader and from the engine)
* @method contextRestored
* @protected
*/
this.contextRestored = function (newGL) {
if (!gl) {
gl = newGL;
thisObj.apply();
}
};
engine.addEventListener('contextLost', this.contextLost);
engine.addEventListener('contextRestored', this.contextRestored);
Object.defineProperties(this, {
/**
* Lookup of uniform based on name.
* for each name a kick.material.UniformDescriptor object exist
* @property lookupUniform
* @type Object
*/
lookupUniform: {
get: function () { return _lookupUniform; }
},
/**
* Array of Object with size,type, name and index properties
* @property activeUniforms
* @type Array_kick.material.UniformDescriptor
*/
activeUniforms: {
get: function () { return _activeUniforms; }
},
/**
* Array of Object with size,type, name and index properties
* @property engineUniforms
* @type Array Array_kick.material.UniformDescriptor
*/
engineUniforms: {
get: function () { return _engineUniforms; }
},
/**
* Array of Object with size,type, name and index properties
* @property materialUniforms
* @type Array Array_kick.material.UniformDescriptor
*/
materialUniforms: {
get: function () { return _materialUniforms; }
},
/**
* @property name
* @type String
*/
name: {
get: function () { return _name; },
set: function (newValue) { _name = newValue; }
},
/**
* When dataURI is specified the shader is expected to have its content from the dataURI.
* This means when serializing the object only dataURI and name will be saved
* @property dataURI
* @type String
*/
dataURI: {
get: function () { return _dataURI; },
set: function (newValue) {
if (_dataURI !== newValue) {
_dataURI = newValue;
if (_dataURI) { // load resource if not null
engine.resourceLoader.getShaderData(_dataURI, thisObj);
}
}
}
},
/**
* Get the gl context of the shader
* @property gl
* @type Object
*/
gl: {
value: gl
},
/**
* Get the gl state
* @property glState
* @type Object
*/
glState: {
value: glState
},
/**
* Get default configuration of shader uniforms
* @property defaultUniforms
* @type Object
*/
defaultUniforms: {
get: function () { return _defaultUniforms; },
set: function (value) {
var name, type;
for (name in _defaultUniforms ) {
if (_defaultUniforms.hasOwnProperty(name) && value.hasOwnProperty(name)) {
type = _lookupUniform[name].type;
_defaultUniforms[name] = Shader.convertUniformValue(type, value[name], engine);
}
}
}
},
/**
* @property vertexShaderSrc
* @type string
*/
vertexShaderSrc: {
get: function () { return _vertexShaderSrc; },
set: function (value) {
if (typeof value !== "string") {
Util.fail("Shader.vertexShaderSrc must be a string");
}
_vertexShaderSrc = value;
}
},
/**
* @property fragmentShaderSrc
* @type string
*/
fragmentShaderSrc: {
get: function () { return _fragmentShaderSrc; },
set: function (value) {
if (typeof value !== "string") {
Util.fail("Shader.fragmentShaderSrc must be a string");
}
_fragmentShaderSrc = value;
}
},
/**
* Render order. Default value 1000. The following ranges are predefined:<br>
* 0-999: Background. Mainly for skyboxes etc<br>
* 1000-1998 Opaque geometry (default)<br>
* 1999-1999 Skybox<br>
* 2000-2999 Transparent. This queue is sorted in a back to front order before rendering.<br>
* 3000-3999 Overlay
* @property renderOrder
* @type Number
*/
renderOrder: {
get: function () { return _renderOrder; },
set: function (value) {
if (typeof value !== "number") {
Util.fail("Shader.renderOrder must be a number");
}
_renderOrder = value;
thisObj.fireEvent('shaderUpdated', thisObj);
}
},
/**
* Function that will be invoked in case of error
* @property errorLog
* @type Function
*/
errorLog: {
get: function () {
return _errorLog;
},
set: function (value) {
if (ASSERT) {
if (value && typeof value !== 'function') {
Util.fail("Shader.errorLog should be a function (or null)");
}
}
_errorLog = value;
}
},
/**
* A reference to the engine object
* @property engine
* @type kick.core.Engine
*/
engine: {
value: engine
},
/**
* @property shaderProgramId
* @type ShaderProgram
*/
shaderProgramId: {
get: function () { return _shaderProgramId; }
},
/**
* (From http://www.opengl.org/)<br>
* When GL\_POLYGON\_OFFSET\_FILL, GL\_POLYGON\_OFFSET\_LINE, or GL\_POLYGON\_OFFSET\_POINT is enabled, each
* fragment's depth value will be offset after it is interpolated from the depth values of the appropriate
* vertices. The value of the offset is factor × DZ + r × units , where DZ is a measurement of the change
* in depth relative to the screen area of the polygon, and r is the smallest value that is guaranteed to
* produce a resolvable offset for a given implementation. The offset is added before the depth test is
* performed and before the value is written into the depth buffer.<br><br>
*
* glPolygonOffset is useful for rendering hidden-line images, for applying decals to surfaces, and for
* rendering solids with highlighted edges.<br><br>
* Possible values:<br>
* true or false<br>
* Default false
* @property polygonOffsetEnabled
* @type boolean
* @default false
*/
polygonOffsetEnabled: {
get: function () {
return _polygonOffsetEnabled;
},
set: function (value) {
_polygonOffsetEnabled = value;
}
},
/**
* @property polygonOffsetFactor
* @type Number
* @default 2.5
*/
polygonOffsetFactor: {
get: function () {
return _polygonOffsetFactor;
},
set: function (value) {
_polygonOffsetFactor = value;
}
},
/**
* @property polygonOffsetUnits
* @type Number
* @default 10.0
*/
polygonOffsetUnits: {
get: function () {
return _polygonOffsetUnits;
},
set: function (value) {
_polygonOffsetUnits = value;
}
},
/**
* Must be set to kick.core.Constants.GL\_FRONT, kick.core.Constants.GL\_BACK (default),
* kick.core.Constants.GL\_FRONT\_AND\_BACK, kick.core.Constants.NONE<br>
* Note that in faceCulling = GL\_FRONT, GL\_BACK or GL\_FRONT\_AND\_BACK with face culling enabled<br>
* faceCulling = GL\_NONE means face culling disabled
* @property faceCulling
* @type Object
* @default Constants.GL_BACK
*/
faceCulling: {
get: function () { return _faceCulling; },
set: function (newValue) {
if (ASSERT) {
if (newValue !== Constants.GL_FRONT &&
newValue !== Constants.GL_FRONT_AND_BACK &&
newValue !== Constants.GL_BACK &&
newValue !== Constants.GL_NONE) {
Util.fail("Shader.faceCulling must be kick.material.Shader.FRONT, " +
"kick.material.Shader.BACK (default), kick.material.Shader.NONE");
}
}
_faceCulling = newValue;
}
},
/**
* Enable or disable writing into the depth buffer
* @property depthMask
* @type Boolean
* @default true
*/
depthMask: {
get: function () { return _depthMask; },
set: function (newValue) {
if (ASSERT) {
if (typeof newValue !== 'boolean') {
Util.fail("Shader.depthMask must be a boolean. Was " + (typeof newValue));
}
}
_depthMask = newValue;
}
},
/**
* The depth test function. Must be one of
* kick.core.Constants.GL\_NEVER,
* kick.core.Constants.GL\_LESS,
* kick.core.Constants.GL\_EQUAL,
* kick.core.Constants.GL\_LEQUAL,
* kick.core.Constants.GL\_GREATER,
* kick.core.Constants.GL\_NOTEQUAL,
* kick.core.Constants.GL\_GEQUAL,
* kick.core.Constants.GL\_ALWAYS
* @property zTest
* @type Object
* @default Constants.GL_LESS
*/
zTest: {
get: function () { return _zTest; },
set: function (newValue) {
if (ASSERT) {
if (newValue !== Constants.GL_NEVER &&
newValue !== Constants.GL_LESS &&
newValue !== Constants.GL_EQUAL &&
newValue !== Constants.GL_LEQUAL &&
newValue !== Constants.GL_GREATER &&
newValue !== Constants.GL_NOTEQUAL &&
newValue !== Constants.GL_GEQUAL &&
newValue !== Constants.GL_ALWAYS) {
Util.fail("Shader.zTest must be kick.core.Constants.GL_NEVER, " +
"kick.core.Constants.GL_LESS,kick.core.Constants.GL_EQUAL,kick.core.Constants.GL_LEQUAL," +
"kick.core.Constants.GL_GREATER,kick.core.Constants.GL_NOTEQUAL,kick.core.Constants.GL_GEQUAL, " +
"or kick.core.Constants.GL_ALWAYS");
}
}
_zTest = newValue;
}
},
/**
* Enables/disables blending (default is false).<br>
* "In RGBA mode, pixels can be drawn using a function that blends the incoming (source) RGBA values with the
* RGBA values that are already in the frame buffer (the destination values)"
* (From <a href="http://www.opengl.org/sdk/docs/man/xhtml/glBlendFunc.xml">www.Opengl.org</a>)
* @property blend
* @type Boolean
*/
blend: {
get: function () { return _blend; },
set: function (value) {
if (ASSERT) {
if (typeof value !== 'boolean') {
Util.fail("Shader.blend must be a boolean");
}
}
_blend = value;
updateBlendKey();
}
},
/**
* Specifies the blend source-factor for the RGB channel<br>
* Initial value GL\_SRC\_ALPHA
* Must be set to one of: GL\_ZERO, GL\_ONE, GL\_SRC\_COLOR, GL\_ONE\_MINUS\_SRC\_COLOR, GL\_DST\_COLOR,
* GL\_ONE\_MINUS\_DST\_COLOR, GL\_SRC\_ALPHA, GL\_ONE\_MINUS\_SRC\_ALPHA, GL\_DST\_ALPHA, GL\_ONE\_MINUS\_DST\_ALPHA,
* GL\_CONSTANT\_COLOR, GL\_ONE\_MINUS\_CONSTANT\_COLOR, GL\_CONSTANT\_ALPHA, GL\_ONE\_MINUS\_CONSTANT\_ALPHA, and
* GL\_SRC\_ALPHA\_SATURATE.<br>
* See <a href="http://www.opengl.org/sdk/docs/man/xhtml/glBlendFunc.xml">glBlendFunc on opengl.org</a>
* @property blendSFactorRGB
* @type Number
* @default Constants.GL_SRC_ALPHA
*/
blendSFactorRGB:{
get: function(){
return _blendSFactorRGB;
},
set: function(value){
if (ASSERT) {
var c = Constants;
if (value !== c.GL_ZERO &&
value !== c.GL_ONE &&
value !== c.GL_SRC_COLOR &&
value !== c.GL_ONE_MINUS_SRC_COLOR &&
value !== c.GL_DST_COLOR &&
value !== c.GL_ONE_MINUS_DST_COLOR &&
value !== c.GL_SRC_ALPHA &&
value !== c.GL_ONE_MINUS_SRC_ALPHA &&
value !== c.GL_DST_ALPHA &&
value !== c.GL_ONE_MINUS_DST_ALPHA &&
value !== c.GL_CONSTANT_COLOR &&
value !== c.GL_ONE_MINUS_CONSTANT_COLOR &&
value !== c.GL_CONSTANT_ALPHA &&
value !== c.GL_ONE_MINUS_CONSTANT_ALPHA &&
value !== c.GL_SRC_ALPHA_SATURATE) {
Util.fail("Shader.blendSFactor must be a one of GL_ZERO, GL_ONE, GL_SRC_COLOR, " +
"GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, " +
"GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_CONSTANT_COLOR, " +
"GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA, and " +
"GL_SRC_ALPHA_SATURATE.");
}
}
_blendSFactorRGB = value;
updateBlendKey();
}
},
/**
* Specifies the blend source-factor for the alpha channel<br>
* Initial value GL\_SRC\_ALPHA
* Must be set to one of: GL\_ZERO, GL\_ONE, GL\_SRC\_COLOR, GL\_ONE\_MINUS\_SRC\_COLOR, GL\_DST\_COLOR,
* GL\_ONE\_MINUS\_DST\_COLOR, GL\_SRC\_ALPHA, GL\_ONE\_MINUS\_SRC\_ALPHA, GL\_DST\_ALPHA, GL\_ONE\_MINUS\_DST\_ALPHA,
* GL\_CONSTANT\_COLOR, GL\_ONE\_MINUS\_CONSTANT\_COLOR, GL\_CONSTANT\_ALPHA, GL\_ONE\_MINUS\_CONSTANT\_ALPHA, and
* GL\_SRC\_ALPHA\_SATURATE.<br>
* See <a href="http://www.opengl.org/sdk/docs/man/xhtml/glBlendFunc.xml">glBlendFunc on opengl.org</a>
* @property blendSFactorAlpha
* @type Number
* @default Constants.GL_SRC_ALPHA
*/
blendSFactorAlpha:{
get: function(){
return _blendSFactorAlpha;
},
set: function(value){
if (ASSERT) {
var c = Constants;
if (value !== c.GL_ZERO &&
value !== c.GL_ONE &&
value !== c.GL_SRC_COLOR &&
value !== c.GL_ONE_MINUS_SRC_COLOR &&
value !== c.GL_DST_COLOR &&
value !== c.GL_ONE_MINUS_DST_COLOR &&
value !== c.GL_SRC_ALPHA &&
value !== c.GL_ONE_MINUS_SRC_ALPHA &&
value !== c.GL_DST_ALPHA &&
value !== c.GL_ONE_MINUS_DST_ALPHA &&
value !== c.GL_CONSTANT_COLOR &&
value !== c.GL_ONE_MINUS_CONSTANT_COLOR &&
value !== c.GL_CONSTANT_ALPHA &&
value !== c.GL_ONE_MINUS_CONSTANT_ALPHA &&
value !== c.GL_SRC_ALPHA_SATURATE) {
Util.fail("Shader.blendSFactorAlpha must be a one of GL_ZERO, GL_ONE, GL_SRC_COLOR, " +
"GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, " +
"GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_CONSTANT_COLOR, " +
"GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA, and " +
"GL_SRC_ALPHA_SATURATE.");
}
}
_blendSFactorAlpha = value;
updateBlendKey();
}
},
/**
* Short for blendSFactorAlpha and blendSFactorRGB
* Specifies the blend s-factor<br>
* Initial value GL\_SRC\_ALPHA
* Must be set to one of: GL\_ZERO, GL\_ONE, GL\_SRC\_COLOR, GL\_ONE\_MINUS\_SRC\_COLOR, GL\_DST\_COLOR,
* GL\_ONE\_MINUS\_DST\_COLOR, GL\_SRC\_ALPHA, GL\_ONE\_MINUS\_SRC\_ALPHA, GL\_DST\_ALPHA, GL\_ONE\_MINUS\_DST\_ALPHA,
* GL\_CONSTANT\_COLOR, GL\_ONE\_MINUS\_CONSTANT\_COLOR, GL\_CONSTANT\_ALPHA, GL\_ONE\_MINUS\_CONSTANT\_ALPHA, and
* GL\_SRC\_ALPHA\_SATURATE.<br>
* See <a href="http://www.opengl.org/sdk/docs/man/xhtml/glBlendFunc.xml">glBlendFunc on opengl.org</a>
* @property blendSFactor
* @type Number
*/
blendSFactor: {
get: function () { return _blendSFactorRGB; },
set: function (value) {
thisObj.blendSFactorAlpha = value;
thisObj.blendSFactorRGB = value;
}
},
/**
* Specifies the blend d-factor for the RGB channel<br>
* Initial value GL\_SRC\_ALPHA
* Must be set to one of: GL\_ZERO, GL\_ONE, GL\_SRC\_COLOR, GL\_ONE\_MINUS\_SRC\_COLOR, GL\_DST\_COLOR,
* GL\_ONE\_MINUS\_DST\_COLOR, GL\_SRC\_ALPHA, GL\_ONE\_MINUS\_SRC\_ALPHA, GL\_DST\_ALPHA, GL\_ONE\_MINUS\_DST\_ALPHA,
* GL\_CONSTANT\_COLOR, GL\_ONE\_MINUS\_CONSTANT\_COLOR, GL\_CONSTANT\_ALPHA, GL\_ONE\_MINUS\_CONSTANT\_ALPHA, and
* GL\_ONE\_MINUS\_SRC\_ALPHA.<br>
* See <a href="http://www.opengl.org/sdk/docs/man/xhtml/glBlendFunc.xml">glBlendFunc on opengl.org</a>
* @property blendDFactorRGB
* @type Number
* @default Constants.GL_ONE_MINUS_SRC_ALPHA
*/
blendDFactorRGB: {
get: function(){
return _blendDFactorRGB;
},
set: function(value){
if (ASSERT) {
var c = Constants;
if (value !== c.GL_ZERO &&
value !== c.GL_ONE &&
value !== c.GL_SRC_COLOR &&
value !== c.GL_ONE_MINUS_SRC_COLOR &&
value !== c.GL_DST_COLOR &&
value !== c.GL_ONE_MINUS_DST_COLOR &&
value !== c.GL_SRC_ALPHA &&
value !== c.GL_ONE_MINUS_SRC_ALPHA &&
value !== c.GL_DST_ALPHA &&
value !== c.GL_ONE_MINUS_DST_ALPHA &&
value !== c.GL_CONSTANT_COLOR &&
value !== c.GL_ONE_MINUS_CONSTANT_COLOR &&
value !== c.GL_CONSTANT_ALPHA &&
value !== c.GL_ONE_MINUS_CONSTANT_ALPHA) {
Util.fail("Shader.blendDFactorRGB must be a one of GL_ZERO, GL_ONE, GL_SRC_COLOR, " +
"GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, " +
"GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_CONSTANT_COLOR, " +
"GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_ALPHA, and GL_ONE_MINUS_CONSTANT_ALPHA.");
}
}
_blendDFactorRGB = value;
updateBlendKey();
}
},
/**
* Specifies the blend d-factor for the alpha channel<br>
* Initial value GL\_SRC\_ALPHA
* Must be set to one of: GL\_ZERO, GL\_ONE, GL\_SRC\_COLOR, GL\_ONE\_MINUS\_SRC\_COLOR, GL\_DST\_COLOR,
* GL\_ONE\_MINUS\_DST\_COLOR, GL\_SRC\_ALPHA, GL\_ONE\_MINUS\_SRC\_ALPHA, GL\_DST\_ALPHA, GL\_ONE\_MINUS\_DST\_ALPHA,
* GL\_CONSTANT\_COLOR, GL\_ONE\_MINUS\_CONSTANT\_COLOR, GL\_CONSTANT\_ALPHA, GL\_ONE\_MINUS\_CONSTANT\_ALPHA, and
* GL\_ONE\_MINUS\_SRC\_ALPHA.<br>
* See <a href="http://www.opengl.org/sdk/docs/man/xhtml/glBlendFunc.xml">glBlendFunc on opengl.org</a>
* @property blendDFactorAlpha
* @type Number
* @default Constants.GL_ONE_MINUS_SRC_ALPHA
*/
blendDFactorAlpha: {
get: function(){
return _blendDFactorAlpha;
},
set: function(value){
if (ASSERT) {
var c = Constants;
if (value !== c.GL_ZERO &&
value !== c.GL_ONE &&
value !== c.GL_SRC_COLOR &&
value !== c.GL_ONE_MINUS_SRC_COLOR &&
value !== c.GL_DST_COLOR &&
value !== c.GL_ONE_MINUS_DST_COLOR &&
value !== c.GL_SRC_ALPHA &&
value !== c.GL_ONE_MINUS_SRC_ALPHA &&
value !== c.GL_DST_ALPHA &&
value !== c.GL_ONE_MINUS_DST_ALPHA &&
value !== c.GL_CONSTANT_COLOR &&
value !== c.GL_ONE_MINUS_CONSTANT_COLOR &&
value !== c.GL_CONSTANT_ALPHA &&
value !== c.GL_ONE_MINUS_CONSTANT_ALPHA) {
Util.fail("Shader.blendDFactorAlpha must be a one of GL_ZERO, GL_ONE, GL_SRC_COLOR, " +
"GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, " +
"GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_CONSTANT_COLOR, " +
"GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_ALPHA, and GL_ONE_MINUS_CONSTANT_ALPHA.");
}
}
_blendDFactorAlpha = value;
updateBlendKey();
}
},
/**
* Short for blendDFactorAlpha and blendDFactorRGB
* Initial value GL\_SRC\_ALPHA
* Must be set to one of: GL\_ZERO, GL\_ONE, GL\_SRC\_COLOR, GL\_ONE\_MINUS\_SRC\_COLOR, GL\_DST\_COLOR,
* GL\_ONE\_MINUS\_DST\_COLOR, GL\_SRC\_ALPHA, GL\_ONE\_MINUS\_SRC\_ALPHA, GL\_DST\_ALPHA, GL\_ONE\_MINUS\_DST\_ALPHA,
* GL\_CONSTANT\_COLOR, GL\_ONE\_MINUS\_CONSTANT\_COLOR, GL\_CONSTANT\_ALPHA, GL\_ONE\_MINUS\_CONSTANT\_ALPHA, and
* GL\_ONE\_MINUS\_SRC\_ALPHA.<br>
* See <a href="http://www.opengl.org/sdk/docs/man/xhtml/glBlendFunc.xml">glBlendFunc on opengl.org</a>
* @property blendDFactor
* @type Number
*/
blendDFactor: {
get: function () { return _blendDFactorRGB; },
set: function (value) {
thisObj.blendDFactorAlpha = value;
thisObj.blendDFactorRGB = value;
}
},
/**
* Unique shader version (this number will change whenever apply is invoked). The value may be different after serialization.
* @property shaderVersion
* @type Number
*/
shaderVersion : {
get: function () {
return uniqueVersionCounter;
}
}
});
/**
* Flush the current shader bound - this force the shader to be reloaded (and its uniforms and vertex attributes
* are reassigned)
* @method markUniformUpdated
*/
this.markUniformUpdated = function () {
glState.boundShader = -1;
glState.meshShader = -1;
};
/**
* Updates the shader (must be called after any shader state is changed to apply changes)
* @method apply
* @return {Boolean} shader created successfully
*/
this.apply = function () {
var errorLog = _errorLog || console.log,
vertexShader = compileShader(_vertexShaderSrc, false, errorLog),
fragmentShader = compileShader(_fragmentShaderSrc, true, errorLog),
compileError = fragmentShader === null || vertexShader === null,
i,
numberOfActiveUniforms,
activeAttributes,
attribute;
if (compileError) {
vertexShader = compileShader(glslConstants["__error_vs.glsl"], false, errorLog);
fragmentShader = compileShader(glslConstants["__error_fs.glsl"], true, errorLog);
}
_shaderProgramId = gl.createProgram();
gl.attachShader(_shaderProgramId, vertexShader);
gl.attachShader(_shaderProgramId, fragmentShader);
gl.linkProgram(_shaderProgramId);
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
if (!gl.getProgramParameter(_shaderProgramId, Constants.GL_LINK_STATUS)) {
compileError = gl.getProgramInfoLog(_shaderProgramId);
errorLog(compileError);
thisObj.fireEvent('linkerError', compileError);
return false;
}
uniqueVersionCounter = (shaderUniqueVersionCounter++);
gl.useProgram(_shaderProgramId);
glState.boundShader = _shaderProgramId;
numberOfActiveUniforms = gl.getProgramParameter(_shaderProgramId, Constants.GL_ACTIVE_UNIFORMS);
updateActiveUniforms(numberOfActiveUniforms);
activeAttributes = gl.getProgramParameter(_shaderProgramId, Constants.GL_ACTIVE_ATTRIBUTES);
/**
* Array of JSON data with size,type and name
* @property activeAttributes
* @type Array_Object
*/
thisObj.activeAttributes = [];
/**
* Lookup of attribute location based on name.
* @property lookupAttribute
* @type Object
*/
thisObj.lookupAttribute = {};
for (i = 0; i < activeAttributes; i++) {
attribute = gl.getActiveAttrib(_shaderProgramId, i);
thisObj.activeAttributes[i] = {
size: attribute.size,
type: attribute.type,
name: attribute.name
};
thisObj.lookupAttribute[attribute.name] = i;
}
thisObj.markUniformUpdated();
thisObj.fireEvent('shaderUpdated', thisObj);
return !compileError;
};
/**
* Deletes the shader program from memory.
* A destroyed shader can be used again if update shader is called
* @method destroy
*/
this.destroy = function () {
if (_shaderProgramId !== -1) {
engine.removeEventListener('contextLost', thisObj.contextLost);
engine.removeEventListener('contextRestored', thisObj.contextRestored);
gl.deleteProgram(_shaderProgramId);
_shaderProgramId = -1;
engine.project.removeResourceDescriptor(thisObj.uid);
}
};
/**
* Return true if the shader compiled successfully and is not destroyed
* @method isValid
* @return {Boolean} is shader valid
*/
this.isValid = function () {
return _shaderProgramId !== -1;
};
/**
* @method bind
*/
this.bind = function () {
if (ASSERT) {
if (!(thisObj.isValid)) {
Util.fail("Cannot bind a shader that is not valid");
}
}
if (glState.boundShader !== _shaderProgramId) {
glState.boundShader = _shaderProgramId;
gl.useProgram(_shaderProgramId);
updateCullFace();
updateDepthProperties();
updateBlending();
updatePolygonOffset();
}
};
/**
* Serializes the data into a JSON object (that can be used as a config parameter in the constructor)<br>
* Note errorLog are not serialized
* @method toJSON
* @return {Object} config element
*/
this.toJSON = function () {
if (_dataURI) {
return {
uid: thisObj.uid,
name: _name,
dataURI: _dataURI
};
}
return {
uid: thisObj.uid,
name: _name,
blend: _blend,
blendSFactorRGB: _blendSFactorRGB,
blendDFactorRGB: _blendDFactorRGB,
blendSFactorAlpha: _blendSFactorAlpha,
blendDFactorAlpha: _blendDFactorAlpha,
dataURI: _dataURI,
depthMask: _depthMask,
faceCulling: _faceCulling,
fragmentShaderSrc: _fragmentShaderSrc,
vertexShaderSrc: _vertexShaderSrc,
polygonOffsetEnabled: _polygonOffsetEnabled,
polygonOffsetFactor: _polygonOffsetFactor,
polygonOffsetUnits: _polygonOffsetUnits,
renderOrder: _renderOrder,
zTest: _zTest,
defaultUniforms: _defaultUniforms
};
};
this.init = function(config){
Util.applyConfig(thisObj, config, ["uid"]);
if (_dataURI && _dataURI.indexOf("memory://") !== 0) {
engine.resourceLoader.getShaderData(_dataURI, thisObj);
} else {
updateBlendKey();
thisObj.apply();
}
if (_name === "") {
_name = "Shader_" + shaderUniqueNameCounter;
shaderUniqueNameCounter++;
}
};
this.init(config);
};
/**
* @method getPrecompiledSource
* @param {String} sourcecode
* @return {String} sourcecode after precompiler
* @static
*/
Shader.getPrecompiledSource = function (sourcecode) {
var engine = EngineSingleton.engine,
name,
source,
version = "#version 100",
lineOffset = 1,
regExpSearch,
indexOfNewline;
if (Constants._DEBUG) {
if (sourcecode === engine){
Util.fail("Shader.getPrecompiledSource() - engine parameter removed");
return null;
}
(function () {
// insert #line nn after each #pragma include to give meaning full lines in error console
var linebreakPosition = [],
position = sourcecode.indexOf('\n'),
i,
nextPosition;
while (position !== -1) {
position++;
linebreakPosition.push(position);
position = sourcecode.indexOf('\n', position);
}
for (i = linebreakPosition.length - 2; i >= 0; i--) {
position = linebreakPosition[i];
nextPosition = linebreakPosition[i + 1];
if (sourcecode.substring(position).indexOf("#pragma include") === 0) {
sourcecode = sourcecode.substring(0, nextPosition) + ("#line " + (i + 2) + "\n") + sourcecode.substring(nextPosition);
}
}
}());
}
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
for (name in GLSLConstants) {
if (GLSLConstants.hasOwnProperty(name)) {
if (typeof name === "string") {
source = GLSLConstants[name];
regExpSearch = "^\\s*#\\s*pragma\\s+include\\s*"+escapeRegExp("\"" + name + "\"")+".*$";
sourcecode = sourcecode.replace(new RegExp(regExpSearch, "gm"), source);
}
}
}
// remove commented out usages of #pragma include
regExpSearch = "//\\s*pragma\\s+include.*$";
sourcecode = sourcecode.replace(new RegExp(regExpSearch, "gm"), "");
// if shader already contain version tag, then reuse this version information
if (sourcecode.indexOf("#version ") === 0) {
indexOfNewline = sourcecode.indexOf('\n');
version = sourcecode.substring(0, indexOfNewline); // save version info
sourcecode = sourcecode.substring(indexOfNewline + 1); // strip version info
lineOffset = 2;
}
sourcecode =
version + "\n" +
"#define SHADOWS " + (engine.config.shadows === true) + "\n" +
"#define LIGHTS " + (engine.config.maxNumerOfLights) + "\n" +
"#line " + lineOffset + "\n" +
sourcecode;
return sourcecode;
};
/**
* Update the material uniform
* @method bindMaterialUniform
* @param material
* @param engineUniforms
*/
Shader.prototype.bindMaterialUniform = function (material, engineUniforms) {
// lookup uniforms
var gl = this.gl,
glState = this.glState,
timeObj,
sceneLights = engineUniforms.sceneLights,
ambientLight,
lookupUniforms = this.lookupUniform,
proj = lookupUniforms._proj,
directionalLightUniform = lookupUniforms._dLight,
directionalLightWorldUniform = lookupUniforms._dLightWorldDir,
pointLightUniform = lookupUniforms["_pLights[0]"],
time = lookupUniforms._time,
viewport = lookupUniforms._viewport,
lightUniformAmbient = lookupUniforms._ambient,
currentTexture = 0,
ambientLlightValue;
currentTexture = material.bind(currentTexture);
if (proj) {
gl.uniformMatrix4fv(proj.location, false, engineUniforms.projectionMatrix);
}
if (lightUniformAmbient) {
ambientLight = sceneLights.ambientLight;
ambientLlightValue = ambientLight !== null ? ambientLight.colorIntensity : vec3Zero;
gl.uniform3fv(lightUniformAmbient.location, ambientLlightValue);
}
if (directionalLightUniform) {
gl.uniformMatrix3fv(directionalLightUniform.location, false, sceneLights.directionalLightData);
}
if (directionalLightWorldUniform) {
gl.uniform3fv(directionalLightWorldUniform.location, sceneLights.directionalLightWorld);
}
if (pointLightUniform) {
gl.uniformMatrix3fv(pointLightUniform.location, false, sceneLights.pointLightData);
}
if (time) {
timeObj = this.engine.time;
gl.uniform1f(time.location, timeObj.time);
}
if (viewport) {
gl.uniform2fv(viewport.location, glState.viewportSize);
}
return currentTexture;
};
/**
* Binds the uniforms to the current shader.
* The uniforms is expected to be in a valid format.
* The method will call Shader.bindMaterialUniform if material uniforms needs to be changed.
* @method bindUniform
* @param {kick.material.Material} material
* @param {Object} engineUniforms
* @param {kick.scene.Transform} transform
*/
Shader.prototype.bindUniform = function (material, engineUniforms, transform) {
var lookupUniform = this.lookupUniform,
gl = this.gl,
glState = this.glState,
modelMatrix = lookupUniform._m,
mv = lookupUniform._mv,
v = lookupUniform._v,
worldCamPos = lookupUniform._worldCamPos,
world2object = lookupUniform._world2object,
mvProj = lookupUniform._mvProj,
norm = lookupUniform._norm,
gameObjectUID = lookupUniform._gameObjectUID,
shadowMapTexture = lookupUniform._shadowMapTexture,
_lightMat = lookupUniform._lightMat,
sceneLights = engineUniforms.sceneLights,
directionalLight = sceneLights.directionalLight,
globalTransform,
i,
uidAsVec4,
modelView,
normalMatrix,
currentTexture = 0;
if (glState.currentMaterial !== material) {
glState.currentMaterial = material;
currentTexture = this.bindMaterialUniform(material, engineUniforms);
}
// mesh instance uniforms
if (modelMatrix || mv || norm) {
globalTransform = transform.getGlobalMatrix();
if (modelMatrix) {
gl.uniformMatrix4fv(modelMatrix.location, false, globalTransform);
}
modelView = Mat4.multiply(tempMat4, engineUniforms.viewMatrix, globalTransform);
if (mv) {
gl.uniformMatrix4fv(mv.location, false, modelView);
}
if (norm) {
// note this can be simplified to
// var normalMatrix = math.Mat4.toMat3(math.Mat3.create(), finalModelView);
// if the modelViewMatrix is orthogonal (non-uniform scale is not applied)
//var normalMatrix = mat3.transpose(mat4.toInverseMat3(finalModelView));
normalMatrix = Mat4.toNormalMat3(tempMat3, modelView);
if (ASSERT) {
if (!normalMatrix) {
Util.fail("Singular matrix");
}
}
gl.uniformMatrix3fv(norm.location, false, normalMatrix);
}
}
if (v){
gl.uniformMatrix4fv(v.location, false, engineUniforms.viewMatrix);
}
if (worldCamPos) {
gl.uniform3fv(worldCamPos.location, engineUniforms.currentCameraTransform.position);
}
if (world2object) {
gl.uniformMatrix4fv(world2object.location, false, transform.getGlobalTRSInverse());
}
if (mvProj) {
globalTransform = globalTransform || transform.getGlobalMatrix();
gl.uniformMatrix4fv(mvProj.location, false, Mat4.multiply(tempMat4, engineUniforms.viewProjectionMatrix, globalTransform));
}
if (gameObjectUID) {
uidAsVec4 = Util.uint32ToVec4(transform.gameObject.uid, tmpVec4);
if (this.engine.time.frame < 3) {
console.log("transform.gameObject.uid " + transform.gameObject.uid);
}
gl.uniform4fv(gameObjectUID.location, uidAsVec4);
}
if (shadowMapTexture && directionalLight && directionalLight.shadowTexture) {
directionalLight.shadowTexture.bind(currentTexture);
gl.uniform1i(shadowMapTexture.location, currentTexture);
currentTexture++;
}
if (_lightMat) {
globalTransform = transform.getGlobalMatrix();
gl.uniformMatrix4fv(_lightMat.location, false, Mat4.multiply(tempMat4, engineUniforms.lightMatrix, globalTransform));
}
};
/*
* If the uniform value is not in a valid format, the uniformValue is converted
* @method convertUniformValue
* @param {Number} type
* @param {Object} uniformValue
* @param {kick.core.Engine} engine
* @static
*/
Shader.convertUniformValue = function (type, uniformValue, engine) {
if (type === Constants.GL_SAMPLER_2D || type === Constants.GL_SAMPLER_CUBE) {
if (uniformValue && typeof uniformValue.ref === 'number') {
return engine.project.load(uniformValue.ref);
}
}
if (Array.isArray(uniformValue) || typeof uniformValue === 'number') {
var array = uniformValue;
if (typeof array === 'number') {
array = [array];
}
if (type === Constants.GL_INT || type === Constants.GL_INT_VEC2 || type === Constants.GL_INT_VEC3 || type === Constants.GL_INT_VEC4) {
return new Int32Array(array);
} else {
return new Float32Array(array);
}
}
return uniformValue;
};
Object.freeze(Shader);
return Shader;
});