var utils = require('../../../utils'), CONST = require('../../../const'); /** * @class * @namespace PIXI * @param shaderManager {ShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. * @param [fragmentSrc] {string} The source of the fragment shader. * @param [uniforms] {object} Uniforms for this shader. * @param [attributes] {obje=ct} Attributes for this shader. */ function Shader(shaderManager, vertexSrc, fragmentSrc, uniforms, attributes) { if (!vertexSrc || !fragmentSrc) { throw new Error('Pixi.js Error. Shader requires vertexSrc and fragmentSrc'); } /** * @member {number} * @readonly */ this.uuid = utils.uuid(); /** * @member {WebGLContext} * @readonly */ this.gl = shaderManager.renderer.gl; /** * The WebGL program. * @member {WebGLProgram} * @readonly */ this.program = null; this.uniforms = uniforms || {}; this.attributes = attributes || {}; this.textureCount = 1; /** * The vertex shader. * @member {Array} */ this.vertexSrc = vertexSrc; /** * The fragment shader. * @member {Array} */ this.fragmentSrc = fragmentSrc; this.init(); } Shader.prototype.constructor = Shader; module.exports = Shader; Shader.prototype.init = function () { this.compile(); this.gl.useProgram(this.program); this.cacheUniformLocations(Object.keys(this.uniforms)); this.cacheAttributeLocations(Object.keys(this.attributes)); }; Shader.prototype.cacheUniformLocations = function (keys) { for (var i = 0; i < keys.length; ++i) { this.uniforms[keys[i]]._location = this.gl.getUniformLocation(this.program, keys[i]); } }; Shader.prototype.cacheAttributeLocations = function (keys) { for (var i = 0; i < keys.length; ++i) { this.attributes[keys[i]] = this.gl.getAttribLocation(this.program, keys[i]); } // TODO: Check if this is needed anymore... // Begin worst hack eva // // WHY??? ONLY on my chrome pixel the line above returns -1 when using filters? // maybe its something to do with the current state of the gl context. // I'm convinced this is a bug in the chrome browser as there is NO reason why this should be returning -1 especially as it only manifests on my chrome pixel // If theres any webGL people that know why could happen please help :) // if (this.attributes.aColor === -1){ // this.attributes.aColor = 2; // } // End worst hack eva // }; Shader.prototype.compile = function () { var gl = this.gl; var glVertShader = this._glCompile(gl.VERTEX_SHADER, this.vertexSrc); var glFragShader = this._glCompile(gl.FRAGMENT_SHADER, this.fragmentSrc); var program = gl.createProgram(); gl.attachShader(program, glVertShader); gl.attachShader(program, glFragShader); gl.linkProgram(program); // if linking fails, then log and cleanup if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { window.console.error('Pixi.js Error: Could not initialize shader.'); window.console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); window.console.error('gl.getError()', gl.getError()); // if there is a program info log, log it if (gl.getProgramInfoLog(program) !== '') { window.console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); } gl.deleteProgram(program); program = null; } // clean up some shaders gl.deleteShader(glVertShader); gl.deleteShader(glFragShader); return (this.program = program); }; /* Shader.prototype.buildSync = function () { // var str = "" // str = "Shader.prototype.syncUniforms = function()"; // str += "{\n"; for (var key in this.uniforms) { var uniform = this.uniforms[key]; Object.defineProperty(this, key, { get: function () { return uniform.value }, set: function (value) { this.setUniform(uniform, value); } }); console.log( makePropSetter( key, " bloop", uniform.type ) ) // Object.def // location = uniform._location, // value = uniform.value, //i, il; // str += "gl.uniform1i(this.uniforms."+ key +"._location, this.uniforms." + key + ".value );\n" } }*/ Shader.prototype.syncUniform = function (uniform) { var location = uniform._location, value = uniform.value, gl = this.gl, i, il; switch (uniform.type) { // single int value case 'i': case '1i': gl.uniform1i(location, value); break; // single float value case 'f': case '1f': gl.uniform1f(location, value); break; // Float32Array(2) or JS Arrray case '2f': gl.uniform2f(location, value[0], value[1]); break; // Float32Array(3) or JS Arrray case '3f': gl.uniform3f(location, value[0], value[1], value[2]); break; // Float32Array(4) or JS Arrray case '4f': gl.uniform4f(location, value[0], value[1], value[2], value[3]); break; // a 2D Point object case 'v2': gl.uniform2f(location, value.x, value.y); break; // a 3D Point object case 'v3': gl.uniform3f(location, value.x, value.y, value.z); break; // a 4D Point object case 'v4': gl.uniform4f(location, value.x, value.y, value.z, value.w); break; // Int32Array or JS Array case '1iv': gl.uniform1iv(location, value); break; // Int32Array or JS Array case '2iv': gl.uniform2iv(location, value); break; // Int32Array or JS Array case '3iv': gl.uniform3iv(location, value); break; // Int32Array or JS Array case '4iv': gl.uniform4iv(location, value); break; // Float32Array or JS Array case '1fv': gl.uniform1fv(location, value); break; // Float32Array or JS Array case '2fv': gl.uniform2fv(location, value); break; // Float32Array or JS Array case '3fv': gl.uniform3fv(location, value); break; // Float32Array or JS Array case '4fv': gl.uniform4fv(location, value); break; // Float32Array or JS Array case 'm2': case 'mat2': case 'Matrix2fv': gl.uniformMatrix2fv(location, uniform.transpose, value); break; // Float32Array or JS Array case 'm3': case 'mat3': case 'Matrix3fv': gl.uniformMatrix3fv(location, uniform.transpose, value); break; // Float32Array or JS Array case 'm4': case 'mat4': case 'Matrix4fv': gl.uniformMatrix4fv(location, uniform.transpose, value); break; // a Color Value case 'c': if (typeof value === 'number') { value = utils.hex2rgb(value); } gl.uniform3f(location, value[0], value[1], value[2]); break; // flat array of integers (JS or typed array) case 'iv1': gl.uniform1iv(location, value); break; // flat array of integers with 3 x N size (JS or typed array) case 'iv': gl.uniform3iv(location, value); break; // flat array of floats (JS or typed array) case 'fv1': gl.uniform1fv(location, value); break; // flat array of floats with 3 x N size (JS or typed array) case 'fv': gl.uniform3fv(location, value); break; // array of 2D Point objects case 'v2v': if (!uniform._array) { uniform._array = new Float32Array(2 * value.length); } for (i = 0, il = value.length; i < il; ++i) { uniform._array[i * 2] = value[i].x; uniform._array[i * 2 + 1] = value[i].y; } gl.uniform2fv(location, uniform._array); break; // array of 3D Point objects case 'v3v': if (!uniform._array) { uniform._array = new Float32Array(3 * value.length); } for (i = 0, il = value.length; i < il; ++i) { uniform._array[i * 3] = value[i].x; uniform._array[i * 3 + 1] = value[i].y; uniform._array[i * 3 + 2] = value[i].z; } gl.uniform3fv(location, uniform._array); break; // array of 4D Point objects case 'v4v': if (!uniform._array) { uniform._array = new Float32Array(4 * value.length); } for (i = 0, il = value.length; i < il; ++i) { uniform._array[i * 4] = value[i].x; uniform._array[i * 4 + 1] = value[i].y; uniform._array[i * 4 + 2] = value[i].z; uniform._array[i * 4 + 3] = value[i].w; } gl.uniform4fv(location, uniform._array); break; // PIXI.Texture case 't': case 'sampler2D': if (!uniform.value || !uniform.value.baseTexture.hasLoaded) { break; } // activate this texture gl.activeTexture(gl['TEXTURE' + this.textureCount]); var texture = uniform.value.baseTexture._glTextures[gl.id]; if (!texture) { this.initSampler2D(uniform); } // bind the texture gl.bindTexture(gl.TEXTURE_2D, uniform.value.baseTexture._glTextures[gl.id]); // set uniform to texture index gl.uniform1i(uniform._location, this.textureCount); // increment next texture id this.textureCount++; break; default: window.console.warn('Pixi.js Shader Warning: Unknown uniform type: ' + uniform.type); } }; Shader.prototype.syncUniforms = function () { this.textureCount = 1; for (var key in this.uniforms) { this.syncUniform(this.uniforms[key]); } }; /** * Initialises a Sampler2D uniform (which may only be available later on after initUniforms once the texture has loaded) * */ Shader.prototype.initSampler2D = function (uniform) { var gl = this.gl; var texture = uniform.value.baseTexture; texture._glTextures[gl.id] = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultipliedAlpha); if (uniform.textureData) { var data = uniform.textureData; // GLTexture = mag linear, min linear_mipmap_linear, wrap repeat + gl.generateMipmap(gl.TEXTURE_2D); // GLTextureLinear = mag/min linear, wrap clamp // GLTextureNearestRepeat = mag/min NEAREST, wrap repeat // GLTextureNearest = mag/min nearest, wrap clamp // AudioTexture = whatever + luminance + width 512, height 2, border 0 // KeyTexture = whatever + luminance + width 256, height 2, border 0 // magFilter can be: gl.LINEAR, gl.LINEAR_MIPMAP_LINEAR or gl.NEAREST // wrapS/T can be: gl.CLAMP_TO_EDGE or gl.REPEAT gl.texImage2D(gl.TEXTURE_2D, 0, data.luminance ? gl.LUMINANCE : gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, data.magFilter ? data.magFilter : gl.LINEAR ); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, data.wrapS ? data.wrapS : gl.CLAMP_TO_EDGE ); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, data.wrapS ? data.wrapS : gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, data.wrapT ? data.wrapT : gl.CLAMP_TO_EDGE); } else { gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); 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); } }; /** * Destroys the shader. * */ Shader.prototype.destroy = function () { this.gl.deleteProgram(this.program); this.gl = null; this.uniforms = null; this.attributes = null; this.vertexSrc = null; this.fragmentSrc = null; }; Shader.prototype._glCompile = function (type, src) { var shader = this.gl.createShader(type); this.gl.shaderSource(shader, src); this.gl.compileShader(shader); if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) { window.console.log(this.gl.getShaderInfoLog(shader)); return null; } return shader; };