/*global define*/
define([
'../Core/defaultValue',
'../Core/defined',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/RuntimeError',
'./AutomaticUniforms',
'./ContextLimits',
'./createUniform',
'./createUniformArray'
], function(
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
RuntimeError,
AutomaticUniforms,
ContextLimits,
createUniform,
createUniformArray) {
'use strict';
var nextShaderProgramId = 0;
/**
* @private
*/
function ShaderProgram(options) {
var modifiedFS = handleUniformPrecisionMismatches(options.vertexShaderText, options.fragmentShaderText);
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._duplicateUniformNames = modifiedFS.duplicateUniformNames;
this._cachedShader = undefined; // Used by ShaderCache
/**
* @private
*/
this.maximumTextureUnitIndex = undefined;
this._vertexShaderSource = options.vertexShaderSource;
this._vertexShaderText = options.vertexShaderText;
this._fragmentShaderSource = options.fragmentShaderSource;
this._fragmentShaderText = modifiedFS.fragmentShaderText;
/**
* @private
*/
this.id = nextShaderProgramId++;
}
ShaderProgram.fromCache = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
//>>includeStart('debug', pragmas.debug);
if (!defined(options.context)) {
throw new DeveloperError('options.context is required.');
}
//>>includeEnd('debug');
return options.context.shaderCache.getShaderProgram(options);
};
ShaderProgram.replaceCache = function(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
//>>includeStart('debug', pragmas.debug);
if (!defined(options.context)) {
throw new DeveloperError('options.context is required.');
}
//>>includeEnd('debug');
return options.context.shaderCache.replaceShaderProgram(options);
};
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;
}
}
});
function extractUniforms(shaderText) {
var uniformNames = [];
var uniformLines = shaderText.match(/uniform.*?(?![^{]*})(?=[=\[;])/g);
if (defined(uniformLines)) {
var len = uniformLines.length;
for (var i = 0; i < len; i++) {
var line = uniformLines[i].trim();
var name = line.slice(line.lastIndexOf(' ') + 1);
uniformNames.push(name);
}
}
return uniformNames;
}
function handleUniformPrecisionMismatches(vertexShaderText, fragmentShaderText) {
// If a uniform exists in both the vertex and fragment shader but with different precision qualifiers,
// give the fragment shader uniform a different name. This fixes shader compilation errors on devices
// that only support mediump in the fragment shader.
var duplicateUniformNames = {};
if (!ContextLimits.highpFloatSupported || !ContextLimits.highpIntSupported) {
var i, j;
var uniformName;
var duplicateName;
var vertexShaderUniforms = extractUniforms(vertexShaderText);
var fragmentShaderUniforms = extractUniforms(fragmentShaderText);
var vertexUniformsCount = vertexShaderUniforms.length;
var fragmentUniformsCount = fragmentShaderUniforms.length;
for (i = 0; i < vertexUniformsCount; i++) {
for (j = 0; j < fragmentUniformsCount; j++) {
if (vertexShaderUniforms[i] === fragmentShaderUniforms[j]) {
uniformName = vertexShaderUniforms[i];
duplicateName = 'czm_mediump_' + uniformName;
// Update fragmentShaderText with renamed uniforms
var re = new RegExp(uniformName + '\\b', 'g');
fragmentShaderText = fragmentShaderText.replace(re, duplicateName);
duplicateUniformNames[duplicateName] = uniformName;
}
}
}
}
return {
fragmentShaderText : fragmentShaderText,
duplicateUniformNames : duplicateUniformNames
};
}
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 = createUniform(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 = createUniformArray(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(shader, uniforms) {
var automaticUniforms = [];
var manualUniforms = [];
for (var uniform in uniforms) {
if (uniforms.hasOwnProperty(uniform)) {
var uniformObject = uniforms[uniform];
var uniformName = uniform;
// if it's a duplicate uniform, use its original name so it is updated correctly
var duplicateUniform = shader._duplicateUniformNames[uniformName];
if (defined(duplicateUniform)) {
uniformObject.name = duplicateUniform;
uniformName = duplicateUniform;
}
var automaticUniform = AutomaticUniforms[uniformName];
if (defined(automaticUniform)) {
automaticUniforms.push({
uniform : uniformObject,
automaticUniform : automaticUniform
});
} else {
manualUniforms.push(uniformObject);
}
}
}
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(shader, 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);
//>>includeStart('debug', pragmas.debug);
if (!gl.getProgramParameter(program, gl.VALIDATE_STATUS)) {
throw new DeveloperError('Program validation failed. Program info log: ' + gl.getProgramInfoLog(program));
}
//>>includeEnd('debug');
}
};
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;
});