diff --git a/Source/Renderer/Context.js b/Source/Renderer/Context.js
index a713077342c5..97fd75210780 100644
--- a/Source/Renderer/Context.js
+++ b/Source/Renderer/Context.js
@@ -263,6 +263,11 @@ define([
ContextLimits._maximumViewportWidth = maximumViewportDimensions[0];
ContextLimits._maximumViewportHeight = maximumViewportDimensions[1];
+ var highpFloat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);
+ ContextLimits._highpFloatSupported = highpFloat.precision !== 0;
+ var highpInt = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_INT);
+ ContextLimits._highpIntSupported = highpInt.rangeMax !== 0;
+
this._antialias = gl.getContextAttributes().antialias;
// Query and initialize extensions
diff --git a/Source/Renderer/ContextLimits.js b/Source/Renderer/ContextLimits.js
index 6dc1736d002a..00c4bebce99a 100644
--- a/Source/Renderer/ContextLimits.js
+++ b/Source/Renderer/ContextLimits.js
@@ -27,7 +27,9 @@ define([
_maximumViewportHeight : 0,
_maximumTextureFilterAnisotropy : 0,
_maximumDrawBuffers : 0,
- _maximumColorAttachments : 0
+ _maximumColorAttachments : 0,
+ _highpFloatSupported: false,
+ _highpIntSupported: false
};
defineProperties(ContextLimits, {
@@ -264,7 +266,30 @@ define([
get: function () {
return ContextLimits._maximumColorAttachments;
}
+ },
+
+ /**
+ * High precision float supported (highp
) in fragment shaders.
+ * @memberof ContextLimits
+ * @type {Boolean}
+ */
+ highpFloatSupported : {
+ get: function () {
+ return ContextLimits._highpFloatSupported;
+ }
+ },
+
+ /**
+ * High precision int supported (highp
) in fragment shaders.
+ * @memberof ContextLimits
+ * @type {Boolean}
+ */
+ highpIntSupported : {
+ get: function () {
+ return ContextLimits._highpIntSupported;
+ }
}
+
});
return ContextLimits;
diff --git a/Source/Renderer/ShaderProgram.js b/Source/Renderer/ShaderProgram.js
index 6ff6ce0a582c..bb5d450e7cfb 100644
--- a/Source/Renderer/ShaderProgram.js
+++ b/Source/Renderer/ShaderProgram.js
@@ -2,21 +2,25 @@
define([
'../Core/defaultValue',
'../Core/defined',
+ '../Core/definedNotNull',
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/RuntimeError',
'./AutomaticUniforms',
+ './ContextLimits',
'./createUniform',
'./createUniformArray'
], function(
defaultValue,
defined,
+ definedNotNull,
defineProperties,
destroyObject,
DeveloperError,
RuntimeError,
AutomaticUniforms,
+ ContextLimits,
createUniform,
createUniformArray) {
"use strict";
@@ -28,6 +32,8 @@ define([
* @private
*/
var ShaderProgram = function(options) {
+ var modifiedFS = handleUniformPrecisionMismatches(options.vertexShaderText, options.fragmentShaderText);
+
this._gl = options.gl;
this._logShaderCompilation = options.logShaderCompilation;
this._debugShaders = options.debugShaders;
@@ -40,6 +46,7 @@ define([
this._uniforms = undefined;
this._automaticUniforms = undefined;
this._manualUniforms = undefined;
+ this._duplicateUniformNames = modifiedFS.duplicateUniformNames;
this._cachedShader = undefined; // Used by ShaderCache
/**
@@ -50,7 +57,7 @@ define([
this._vertexShaderSource = options.vertexShaderSource;
this._vertexShaderText = options.vertexShaderText;
this._fragmentShaderSource = options.fragmentShaderSource;
- this._fragmentShaderText = options.fragmentShaderText;
+ this._fragmentShaderText = modifiedFS.fragmentShaderText;
/**
* @private
@@ -127,6 +134,55 @@ define([
}
});
+ function extractUniforms(shaderText) {
+ var uniformNames = [];
+ var uniformLines = shaderText.match(/uniform.*?(?![^{]*})(?=[=\[;])/g);
+ if (definedNotNull(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) {
@@ -346,20 +402,28 @@ define([
};
}
- function partitionUniforms(uniforms) {
+ function partitionUniforms(shader, uniforms) {
var automaticUniforms = [];
var manualUniforms = [];
- for ( var uniform in uniforms) {
+ for (var uniform in uniforms) {
if (uniforms.hasOwnProperty(uniform)) {
- var automaticUniform = AutomaticUniforms[uniform];
- if (automaticUniform) {
+ 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 : uniforms[uniform],
+ uniform : uniformObject,
automaticUniform : automaticUniform
});
} else {
- manualUniforms.push(uniforms[uniform]);
+ manualUniforms.push(uniformObject);
}
}
}
@@ -393,7 +457,7 @@ define([
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);
+ var partitionedUniforms = partitionUniforms(shader, uniforms.uniformsByName);
shader._program = program;
shader._numberOfVertexAttributes = numberOfVertexAttributes;
diff --git a/Source/Renderer/ShaderSource.js b/Source/Renderer/ShaderSource.js
index beef3d4bc975..fd1827ae2263 100644
--- a/Source/Renderer/ShaderSource.js
+++ b/Source/Renderer/ShaderSource.js
@@ -14,6 +14,9 @@ define([
"use strict";
function removeComments(source) {
+ // remove inline comments
+ source = source.replace(/\/\/.*/g, '');
+ // remove multiline comment block
return source.replace(/\/\*\*[\s\S]*?\*\//gm, function(match) {
// preserve the number of lines in the comment block so the line numbers will be correct when debugging shaders
var numberOfLines = match.match(/\n/gm).length;
diff --git a/Specs/Renderer/ShaderProgramSpec.js b/Specs/Renderer/ShaderProgramSpec.js
index b74b43a19de8..b263077425da 100644
--- a/Specs/Renderer/ShaderProgramSpec.js
+++ b/Specs/Renderer/ShaderProgramSpec.js
@@ -12,6 +12,7 @@ defineSuite([
'Renderer/Buffer',
'Renderer/BufferUsage',
'Renderer/ClearCommand',
+ 'Renderer/ContextLimits',
'Renderer/DrawCommand',
'Renderer/ShaderSource',
'Renderer/VertexArray',
@@ -29,6 +30,7 @@ defineSuite([
Buffer,
BufferUsage,
ClearCommand,
+ ContextLimits,
DrawCommand,
ShaderSource,
VertexArray,
@@ -77,7 +79,7 @@ defineSuite([
}
});
- function renderFragment(context, shaderProgram) {
+ function renderFragment(context, shaderProgram, uniformMap) {
va = new VertexArray({
context : context,
attributes : [{
@@ -97,7 +99,8 @@ defineSuite([
var command = new DrawCommand({
primitiveType : PrimitiveType.POINTS,
shaderProgram : shaderProgram,
- vertexArray : va
+ vertexArray : va,
+ uniformMap : uniformMap
});
command.execute(context);
@@ -355,6 +358,27 @@ defineSuite([
expect(renderFragment(context, sp)).toEqual([255, 255, 255, 255]);
});
+ it('creates duplicate uniforms if precision of uniforms in vertex and fragment shader do not match', function() {
+ var highpFloatSupported = ContextLimits.highpFloatSupported;
+ ContextLimits._highpFloatSupported = false;
+ var vs = 'attribute vec4 position; uniform float u_value; varying float v_value; void main() { gl_PointSize = 1.0; v_value = u_value * czm_viewport.z; gl_Position = position; }';
+ var fs = 'uniform float u_value; varying float v_value; void main() { gl_FragColor = vec4(u_value * v_value * czm_viewport.z); }';
+ sp = ShaderProgram.fromCache({
+ context : context,
+ vertexShaderSource : vs,
+ fragmentShaderSource : fs
+ });
+ var uniformMap = {
+ u_value : function() {
+ return 1.0;
+ }
+ };
+ expect(sp.allUniforms.u_value).toBeDefined();
+ expect(sp.allUniforms.czm_mediump_u_value).toBeDefined();
+ expect(renderFragment(context, sp, uniformMap)).not.toEqual([0, 0, 0, 0]);
+ ContextLimits._highpFloatSupported = highpFloatSupported;
+ });
+
it('1 level function dependency', function() {
var vs = 'attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }';
var fs =