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 =