/*global define*/ define([ '../Core/defined', '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', '../Core/RuntimeError', './AutomaticUniforms', './Uniform', './UniformArray' ], function( defined, defineProperties, destroyObject, DeveloperError, RuntimeError, AutomaticUniforms, Uniform, UniformArray) { "use strict"; /*global console*/ var nextShaderProgramId = 0; /** * @private */ var ShaderProgram = function(options) { this._gl = options.gl; this._logShaderCompilation = options.logShaderCompilation; this._debugShaders = options.debugShaders; this._attributeLocations = options.attributeLocations; this._program = undefined; this._numberOfVertexAttributes = undefined; this._vertexAttributes = undefined; this._uniformsByName = undefined; this._uniforms = undefined; this._automaticUniforms = undefined; this._manualUniforms = undefined; this._cachedShader = undefined; // Used by ShaderCache /** * @private */ this.maximumTextureUnitIndex = undefined; this._vertexShaderSource = options.vertexShaderSource; this._vertexShaderText = options.vertexShaderText; this._fragmentShaderSource = options.fragmentShaderSource; this._fragmentShaderText = options.fragmentShaderText; /** * @private */ this.id = nextShaderProgramId++; }; defineProperties(ShaderProgram.prototype, { /** * GLSL source for the shader program's vertex shader. * @memberof ShaderProgram.prototype * * @type {ShaderSource} * @readonly */ vertexShaderSource : { get : function() { return this._vertexShaderSource; } }, /** * GLSL source for the shader program's fragment shader. * @memberof ShaderProgram.prototype * * @type {ShaderSource} * @readonly */ fragmentShaderSource : { get : function() { return this._fragmentShaderSource; } }, vertexAttributes : { get : function() { initialize(this); return this._vertexAttributes; } }, numberOfVertexAttributes : { get : function() { initialize(this); return this._numberOfVertexAttributes; } }, allUniforms : { get : function() { initialize(this); return this._uniformsByName; } } }); var consolePrefix = '[Cesium WebGL] '; function createAndLinkProgram(gl, shader) { var vsSource = shader._vertexShaderText; var fsSource = shader._fragmentShaderText; var vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vsSource); gl.compileShader(vertexShader); var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fsSource); gl.compileShader(fragmentShader); var program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.deleteShader(vertexShader); gl.deleteShader(fragmentShader); var attributeLocations = shader._attributeLocations; if (defined(attributeLocations)) { for ( var attribute in attributeLocations) { if (attributeLocations.hasOwnProperty(attribute)) { gl.bindAttribLocation(program, attributeLocations[attribute], attribute); } } } gl.linkProgram(program); var log; if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { var debugShaders = shader._debugShaders; // For performance, only check compile errors if there is a linker error. if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { log = gl.getShaderInfoLog(fragmentShader); console.error(consolePrefix + 'Fragment shader compile log: ' + log); if (defined(debugShaders)) { var fragmentSourceTranslation = debugShaders.getTranslatedShaderSource(fragmentShader); if (fragmentSourceTranslation !== '') { console.error(consolePrefix + 'Translated fragment shader source:\n' + fragmentSourceTranslation); } else { console.error(consolePrefix + 'Fragment shader translation failed.'); } } gl.deleteProgram(program); throw new RuntimeError('Fragment shader failed to compile. Compile log: ' + log); } if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { log = gl.getShaderInfoLog(vertexShader); console.error(consolePrefix + 'Vertex shader compile log: ' + log); if (defined(debugShaders)) { var vertexSourceTranslation = debugShaders.getTranslatedShaderSource(vertexShader); if (vertexSourceTranslation !== '') { console.error(consolePrefix + 'Translated vertex shader source:\n' + vertexSourceTranslation); } else { console.error(consolePrefix + 'Vertex shader translation failed.'); } } gl.deleteProgram(program); throw new RuntimeError('Vertex shader failed to compile. Compile log: ' + log); } log = gl.getProgramInfoLog(program); console.error(consolePrefix + 'Shader program link log: ' + log); if (defined(debugShaders)) { console.error(consolePrefix + 'Translated vertex shader source:\n' + debugShaders.getTranslatedShaderSource(vertexShader)); console.error(consolePrefix + 'Translated fragment shader source:\n' + debugShaders.getTranslatedShaderSource(fragmentShader)); } gl.deleteProgram(program); throw new RuntimeError('Program failed to link. Link log: ' + log); } var logShaderCompilation = shader._logShaderCompilation; if (logShaderCompilation) { log = gl.getShaderInfoLog(vertexShader); if (defined(log) && (log.length > 0)) { console.log(consolePrefix + 'Vertex shader compile log: ' + log); } } if (logShaderCompilation) { log = gl.getShaderInfoLog(fragmentShader); if (defined(log) && (log.length > 0)) { console.log(consolePrefix + 'Fragment shader compile log: ' + log); } } if (logShaderCompilation) { log = gl.getProgramInfoLog(program); if (defined(log) && (log.length > 0)) { console.log(consolePrefix + 'Shader program link log: ' + log); } } return program; } function findVertexAttributes(gl, program, numberOfAttributes) { var attributes = {}; for (var i = 0; i < numberOfAttributes; ++i) { var attr = gl.getActiveAttrib(program, i); var location = gl.getAttribLocation(program, attr.name); attributes[attr.name] = { name : attr.name, type : attr.type, index : location }; } return attributes; } function findUniforms(gl, program) { var uniformsByName = {}; var uniforms = []; var samplerUniforms = []; var numberOfUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); for (var i = 0; i < numberOfUniforms; ++i) { var activeUniform = gl.getActiveUniform(program, i); var suffix = '[0]'; var uniformName = activeUniform.name.indexOf(suffix, activeUniform.name.length - suffix.length) !== -1 ? activeUniform.name.slice(0, activeUniform.name.length - 3) : activeUniform.name; // Ignore GLSL built-in uniforms returned in Firefox. if (uniformName.indexOf('gl_') !== 0) { if (activeUniform.name.indexOf('[') < 0) { // Single uniform var location = gl.getUniformLocation(program, uniformName); // IE 11.0.9 needs this check since getUniformLocation can return null // if the uniform is not active (e.g., it is optimized out). Looks like // getActiveUniform() above returns uniforms that are not actually active. if (location !== null) { var uniform = new Uniform(gl, activeUniform, uniformName, location); uniformsByName[uniformName] = uniform; uniforms.push(uniform); if (uniform._setSampler) { samplerUniforms.push(uniform); } } } else { // Uniform array var uniformArray; var locations; var value; var loc; // On some platforms - Nexus 4 in Firefox for one - an array of sampler2D ends up being represented // as separate uniforms, one for each array element. Check for and handle that case. var indexOfBracket = uniformName.indexOf('['); if (indexOfBracket >= 0) { // We're assuming the array elements show up in numerical order - it seems to be true. uniformArray = uniformsByName[uniformName.slice(0, indexOfBracket)]; // Nexus 4 with Android 4.3 needs this check, because it reports a uniform // with the strange name webgl_3467e0265d05c3c1[1] in our globe surface shader. if (!defined(uniformArray)) { continue; } locations = uniformArray._locations; // On the Nexus 4 in Chrome, we get one uniform per sampler, just like in Firefox, // but the size is not 1 like it is in Firefox. So if we push locations here, // we'll end up adding too many locations. if (locations.length <= 1) { value = uniformArray.value; loc = gl.getUniformLocation(program, uniformName); // Workaround for IE 11.0.9. See above. if (loc !== null) { locations.push(loc); value.push(gl.getUniform(program, loc)); } } } else { locations = []; for (var j = 0; j < activeUniform.size; ++j) { loc = gl.getUniformLocation(program, uniformName + '[' + j + ']'); // Workaround for IE 11.0.9. See above. if (loc !== null) { locations.push(loc); } } uniformArray = new UniformArray(gl, activeUniform, uniformName, locations); uniformsByName[uniformName] = uniformArray; uniforms.push(uniformArray); if (uniformArray._setSampler) { samplerUniforms.push(uniformArray); } } } } } return { uniformsByName : uniformsByName, uniforms : uniforms, samplerUniforms : samplerUniforms }; } function partitionUniforms(uniforms) { var automaticUniforms = []; var manualUniforms = []; for ( var uniform in uniforms) { if (uniforms.hasOwnProperty(uniform)) { var automaticUniform = AutomaticUniforms[uniform]; if (automaticUniform) { automaticUniforms.push({ uniform : uniforms[uniform], automaticUniform : automaticUniform }); } else { manualUniforms.push(uniforms[uniform]); } } } return { automaticUniforms : automaticUniforms, manualUniforms : manualUniforms }; } function setSamplerUniforms(gl, program, samplerUniforms) { gl.useProgram(program); var textureUnitIndex = 0; var length = samplerUniforms.length; for (var i = 0; i < length; ++i) { textureUnitIndex = samplerUniforms[i]._setSampler(textureUnitIndex); } gl.useProgram(null); return textureUnitIndex; } function initialize(shader) { if (defined(shader._program)) { return; } var gl = shader._gl; var program = createAndLinkProgram(gl, shader, shader._debugShaders); var numberOfVertexAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); var uniforms = findUniforms(gl, program); var partitionedUniforms = partitionUniforms(uniforms.uniformsByName); shader._program = program; shader._numberOfVertexAttributes = numberOfVertexAttributes; shader._vertexAttributes = findVertexAttributes(gl, program, numberOfVertexAttributes); shader._uniformsByName = uniforms.uniformsByName; shader._uniforms = uniforms.uniforms; shader._automaticUniforms = partitionedUniforms.automaticUniforms; shader._manualUniforms = partitionedUniforms.manualUniforms; shader.maximumTextureUnitIndex = setSamplerUniforms(gl, program, uniforms.samplerUniforms); } ShaderProgram.prototype._bind = function() { initialize(this); this._gl.useProgram(this._program); }; ShaderProgram.prototype._setUniforms = function(uniformMap, uniformState, validate) { var len; var i; if (defined(uniformMap)) { var manualUniforms = this._manualUniforms; len = manualUniforms.length; for (i = 0; i < len; ++i) { var mu = manualUniforms[i]; mu.value = uniformMap[mu.name](); } } var automaticUniforms = this._automaticUniforms; len = automaticUniforms.length; for (i = 0; i < len; ++i) { var au = automaticUniforms[i]; au.uniform.value = au.automaticUniform.getValue(uniformState); } /////////////////////////////////////////////////////////////////// // It appears that assigning the uniform values above and then setting them here // (which makes the GL calls) is faster than removing this loop and making // the GL calls above. I suspect this is because each GL call pollutes the // L2 cache making our JavaScript and the browser/driver ping-pong cache lines. var uniforms = this._uniforms; len = uniforms.length; for (i = 0; i < len; ++i) { uniforms[i]._set(); } if (validate) { var gl = this._gl; var program = this._program; gl.validateProgram(program); if (!gl.getProgramParameter(program, gl.VALIDATE_STATUS)) { throw new DeveloperError('Program validation failed. Program info log: ' + gl.getProgramInfoLog(program)); } } }; ShaderProgram.prototype.isDestroyed = function() { return false; }; ShaderProgram.prototype.destroy = function() { this._cachedShader.cache.releaseShaderProgram(this); return undefined; }; ShaderProgram.prototype.finalDestroy = function() { this._gl.deleteProgram(this._program); return destroyObject(this); }; return ShaderProgram; });