diff --git a/Source/Renderer/Context.js b/Source/Renderer/Context.js index 7eff7dc3fa52..5ab984919331 100644 --- a/Source/Renderer/Context.js +++ b/Source/Renderer/Context.js @@ -269,12 +269,15 @@ define([ // Query and initialize extensions this._standardDerivatives = !!getExtension(gl, ['OES_standard_derivatives']); + this._blendMinmax = !!getExtension(gl, ['EXT_blend_minmax']); this._elementIndexUint = !!getExtension(gl, ['OES_element_index_uint']); this._depthTexture = !!getExtension(gl, ['WEBGL_depth_texture', 'WEBKIT_WEBGL_depth_texture']); this._textureFloat = !!getExtension(gl, ['OES_texture_float']); this._fragDepth = !!getExtension(gl, ['EXT_frag_depth']); this._debugShaders = getExtension(gl, ['WEBGL_debug_shaders']); + this._colorBufferFloat = this._webgl2 && !!getExtension(gl, ['EXT_color_buffer_float']); + this._s3tc = !!getExtension(gl, ['WEBGL_compressed_texture_s3tc', 'MOZ_WEBGL_compressed_texture_s3tc', 'WEBKIT_WEBGL_compressed_texture_s3tc']); this._pvrtc = !!getExtension(gl, ['WEBGL_compressed_texture_pvrtc', 'WEBKIT_WEBGL_compressed_texture_pvrtc']); this._etc1 = !!getExtension(gl, ['WEBGL_compressed_texture_etc1']); @@ -483,7 +486,21 @@ define([ */ standardDerivatives : { get : function() { - return this._standardDerivatives; + return this._standardDerivatives || this._webgl2; + } + }, + + /** + * true if the EXT_blend_minmax extension is supported. This + * extension extends blending capabilities by adding two new blend equations: + * the minimum or maximum color components of the source and destination colors. + * @memberof Context.prototype + * @type {Boolean} + * @see {@link https://www.khronos.org/registry/webgl/extensions/EXT_blend_minmax/} + */ + blendMinmax : { + get : function() { + return this._blendMinmax || this._webgl2; } }, @@ -510,7 +527,7 @@ define([ */ depthTexture : { get : function() { - return this._depthTexture; + return this._depthTexture || this._webgl2; } }, @@ -523,7 +540,7 @@ define([ */ floatingPointTexture : { get : function() { - return this._textureFloat; + return this._textureFloat || this._colorBufferFloat; } }, @@ -597,7 +614,7 @@ define([ */ fragmentDepth : { get : function() { - return this._fragDepth; + return this._fragDepth || this._webgl2; } }, @@ -614,6 +631,20 @@ define([ } }, + /** + * true if the EXT_color_buffer_float extension is supported. This + * extension makes the formats gl.R16F, gl.RG16F, gl.RGBA16F, gl.R32F, gl.RG32F, + * gl.RGBA32F, gl.R11F_G11F_B10F color renderable. + * @memberof Context.prototype + * @type {Boolean} + * @see {@link https://www.khronos.org/registry/webgl/extensions/EXT_color_buffer_float/} + */ + colorBufferFloat : { + get : function() { + return this._colorBufferFloat; + } + }, + /** * true if the WEBGL_draw_buffers extension is supported. This * extensions provides support for multiple render targets. The framebuffer object can have mutiple @@ -1076,7 +1107,8 @@ define([ }), uniformMap : overrides.uniformMap, owner : overrides.owner, - framebuffer : overrides.framebuffer + framebuffer : overrides.framebuffer, + pass : overrides.pass }); }; diff --git a/Source/Renderer/RenderState.js b/Source/Renderer/RenderState.js index 3b30bebbd290..07ab7633fe12 100644 --- a/Source/Renderer/RenderState.js +++ b/Source/Renderer/RenderState.js @@ -21,7 +21,9 @@ define([ function validateBlendEquation(blendEquation) { return ((blendEquation === WebGLConstants.FUNC_ADD) || (blendEquation === WebGLConstants.FUNC_SUBTRACT) || - (blendEquation === WebGLConstants.FUNC_REVERSE_SUBTRACT)); + (blendEquation === WebGLConstants.FUNC_REVERSE_SUBTRACT) || + (blendEquation === WebGLConstants.MIN) || + (blendEquation === WebGLConstants.MAX)); } function validateBlendFunction(blendFunction) { diff --git a/Source/Renderer/ShaderCache.js b/Source/Renderer/ShaderCache.js index d8635213de6d..a2a9e86d7358 100644 --- a/Source/Renderer/ShaderCache.js +++ b/Source/Renderer/ShaderCache.js @@ -96,8 +96,8 @@ define([ }); } - var vertexShaderText = vertexShaderSource.createCombinedVertexShader(); - var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(); + var vertexShaderText = vertexShaderSource.createCombinedVertexShader(this._context); + var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(this._context); var keyword = vertexShaderText + fragmentShaderText + JSON.stringify(attributeLocations); var cachedShader; @@ -169,10 +169,11 @@ define([ }); } - var vertexShaderText = vertexShaderSource.createCombinedVertexShader(); - var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(); - var context = this._context; + + var vertexShaderText = vertexShaderSource.createCombinedVertexShader(context); + var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(context); + var derivedShaderProgram = new ShaderProgram({ gl : context._gl, logShaderCompilation : context.logShaderCompilation, diff --git a/Source/Renderer/ShaderSource.js b/Source/Renderer/ShaderSource.js index 36bb4637145b..b1f4684b93df 100644 --- a/Source/Renderer/ShaderSource.js +++ b/Source/Renderer/ShaderSource.js @@ -2,12 +2,14 @@ define([ '../Core/defaultValue', '../Core/defined', '../Core/DeveloperError', + '../Renderer/modernizeShader', '../Shaders/Builtin/CzmBuiltins', './AutomaticUniforms' ], function( defaultValue, defined, DeveloperError, + modernizeShader, CzmBuiltins, AutomaticUniforms) { 'use strict'; @@ -151,7 +153,7 @@ define([ return builtinsSource.replace(root.glslSource, ''); } - function combineShader(shaderSource, isFragmentShader) { + function combineShader(shaderSource, isFragmentShader, context) { var i; var length; @@ -186,6 +188,17 @@ define([ return '\n'; }); + // Extract shader extensions from sources + var extensions = []; + combinedSources = combinedSources.replace(/#extension.*\n/gm, function(match) { + // Extract extension to put at the top + extensions.push(match); + + // Replace original #extension directive with a new line so the line numbers + // are not off by one. + return '\n'; + }); + // Remove precision qualifier combinedSources = combinedSources.replace(/precision\s(lowp|mediump|highp)\s(float|int);/, ''); @@ -204,6 +217,11 @@ define([ result = '#version ' + version + '\n'; } + var extensionsLength = extensions.length; + for (i = 0; i < extensionsLength; i++) { + result += extensions[i]; + } + if (isFragmentShader) { result += '\ #ifdef GL_FRAGMENT_PRECISION_HIGH\n\ @@ -224,6 +242,12 @@ define([ } } + // GLSLModernizer inserts its own layout qualifiers + // at this position in the source + if (context.webgl2) { + result += '#define OUTPUT_DECLARATION\n\n'; + } + // append built-ins if (shaderSource.includeBuiltIns) { result += getBuiltinsAndAutomaticUniforms(combinedSources); @@ -235,6 +259,11 @@ define([ // append actual source result += combinedSources; + // modernize the source + if (context.webgl2) { + result = modernizeShader(result, isFragmentShader, true); + } + return result; } @@ -297,19 +326,23 @@ define([ /** * Create a single string containing the full, combined vertex shader with all dependencies and defines. * + * @param {Context} context The current rendering context + * * @returns {String} The combined shader string. */ - ShaderSource.prototype.createCombinedVertexShader = function() { - return combineShader(this, false); + ShaderSource.prototype.createCombinedVertexShader = function(context) { + return combineShader(this, false, context); }; /** * Create a single string containing the full, combined fragment shader with all dependencies and defines. * + * @param {Context} context The current rendering context + * * @returns {String} The combined shader string. */ - ShaderSource.prototype.createCombinedFragmentShader = function() { - return combineShader(this, true); + ShaderSource.prototype.createCombinedFragmentShader = function(context) { + return combineShader(this, true, context); }; /** diff --git a/Source/Renderer/Texture.js b/Source/Renderer/Texture.js index 2de068ea749e..34d71a6d56a6 100644 --- a/Source/Renderer/Texture.js +++ b/Source/Renderer/Texture.js @@ -71,6 +71,23 @@ define([ internalFormat = WebGLConstants.DEPTH_COMPONENT24; } } + + if (pixelDatatype === PixelDatatype.FLOAT) { + switch (pixelFormat) { + case PixelFormat.RGBA: + internalFormat = WebGLConstants.RGBA32F; + break; + case PixelFormat.RGB: + internalFormat = WebGLConstants.RGB32F; + break; + case PixelFormat.RG: + internalFormat = WebGLConstants.RG32F; + break; + case PixelFormat.R: + internalFormat = WebGLConstants.R32F; + break; + } + } } //>>includeStart('debug', pragmas.debug); diff --git a/Source/Renderer/modernizeShader.js b/Source/Renderer/modernizeShader.js new file mode 100644 index 000000000000..b2accdf7e62a --- /dev/null +++ b/Source/Renderer/modernizeShader.js @@ -0,0 +1,227 @@ +define([ + '../Core/defined', + '../Core/DeveloperError', + ], function( + defined, + DeveloperError) { + 'use strict'; + + /** + * A function to port GLSL shaders from GLSL ES 1.00 to GLSL ES 3.00 + * + * This function is nowhere near comprehensive or complete. It just + * handles some common cases. + * + * Note that this function requires the presence of the + * "#define OUTPUT_DECLARATION" line that is appended + * by ShaderSource. + * + * @private + */ + function modernizeShader(source, isFragmentShader) { + var outputDeclarationRegex = /#define OUTPUT_DECLARATION/; + var splitSource = source.split('\n'); + + if (/#version 300 es/g.test(source)) { + return source; + } + + var outputDeclarationLine = -1; + var i, line; + for (i = 0; i < splitSource.length; ++i) { + line = splitSource[i]; + if (outputDeclarationRegex.test(line)) { + outputDeclarationLine = i; + break; + } + } + + if (outputDeclarationLine === -1) { + throw new DeveloperError('Could not find a #define OUTPUT_DECLARATION!'); + } + + var outputVariables = []; + + for (i = 0; i < 10; i++) { + var fragDataString = 'gl_FragData\\[' + i + '\\]'; + var newOutput = 'czm_out' + i; + var regex = new RegExp(fragDataString, 'g'); + if (regex.test(source)) { + setAdd(newOutput, outputVariables); + replaceInSourceString(fragDataString, newOutput, splitSource); + splitSource.splice(outputDeclarationLine, 0, 'layout(location = ' + i + ') out vec4 ' + newOutput + ';'); + outputDeclarationLine += 1; + } + } + + var czmFragColor = 'czm_fragColor'; + if (findInSource('gl_FragColor', splitSource)) { + setAdd(czmFragColor, outputVariables); + replaceInSourceString('gl_FragColor', czmFragColor, splitSource); + splitSource.splice(outputDeclarationLine, 0, 'layout(location = 0) out vec4 czm_fragColor;'); + outputDeclarationLine += 1; + } + + var variableMap = getVariablePreprocessorBranch(outputVariables, splitSource); + var lineAdds = {}; + for (i = 0; i < splitSource.length; i++) { + line = splitSource[i]; + for (var variable in variableMap) { + if (variableMap.hasOwnProperty(variable)) { + var matchVar = new RegExp('(layout)[^]+(out)[^]+(' + variable + ')[^]+', 'g'); + if (matchVar.test(line)) { + lineAdds[line] = variable; + } + } + } + } + + for (var layoutDeclaration in lineAdds) { + if (lineAdds.hasOwnProperty(layoutDeclaration)) { + var variableName = lineAdds[layoutDeclaration]; + var lineNumber = splitSource.indexOf(layoutDeclaration); + var entry = variableMap[variableName]; + var depth = entry.length; + var d; + for (d = 0; d < depth; d++) { + splitSource.splice(lineNumber, 0, entry[d]); + } + lineNumber += depth + 1; + for (d = depth - 1; d >= 0; d--) { + splitSource.splice(lineNumber, 0, '#endif //' + entry[d]); + } + } + } + + var versionThree = '#version 300 es'; + var foundVersion = false; + for (i = 0; i < splitSource.length; i++) { + if (/#version/.test(splitSource[i])) { + splitSource[i] = versionThree; + foundVersion = true; + } + } + + if (!foundVersion) { + splitSource.splice(0, 0, versionThree); + } + + removeExtension('EXT_draw_buffers', splitSource); + removeExtension('EXT_frag_depth', splitSource); + + replaceInSourceString('texture2D', 'texture', splitSource); + replaceInSourceString('texture3D', 'texture', splitSource); + replaceInSourceString('textureCube', 'texture', splitSource); + replaceInSourceString('gl_FragDepthEXT', 'gl_FragDepth', splitSource); + + if (isFragmentShader) { + replaceInSourceString('varying', 'in', splitSource); + } else { + replaceInSourceString('attribute', 'in', splitSource); + replaceInSourceString('varying', 'out', splitSource); + } + + return compileSource(splitSource); + } + + // Note that this fails if your string looks like + // searchString[singleCharacter]searchString + function replaceInSourceString(str, replacement, splitSource) { + var regexStr = '(^|[^\\w])(' + str + ')($|[^\\w])'; + var regex = new RegExp(regexStr, 'g'); + + var splitSourceLength = splitSource.length; + for (var i = 0; i < splitSourceLength; ++i) { + var line = splitSource[i]; + splitSource[i] = line.replace(regex, '$1' + replacement + '$3'); + } + } + + function replaceInSourceRegex(regex, replacement, splitSource) { + var splitSourceLength = splitSource.length; + for (var i = 0; i < splitSourceLength; ++i) { + var line = splitSource[i]; + splitSource[i] = line.replace(regex, replacement); + } + } + + function findInSource(str, splitSource) { + var regexStr = '(^|[^\\w])(' + str + ')($|[^\\w])'; + var regex = new RegExp(regexStr, 'g'); + + var splitSourceLength = splitSource.length; + for (var i = 0; i < splitSourceLength; ++i) { + var line = splitSource[i]; + if (regex.test(line)) { + return true; + } + } + return false; + } + + function compileSource(splitSource) { + var wholeSource = ''; + + var splitSourceLength = splitSource.length; + for (var i = 0; i < splitSourceLength; ++i) { + wholeSource += splitSource[i] + '\n'; + } + return wholeSource; + } + + function setAdd(variable, set) { + if (set.indexOf(variable) === -1) { + set.push(variable); + } + } + + function getVariablePreprocessorBranch(layoutVariables, splitSource) { + var variableMap = {}; + + var numLayoutVariables = layoutVariables.length; + + var stack = []; + for (var i = 0; i < splitSource.length; ++i) { + var line = splitSource[i]; + var hasIF = /(#ifdef|#if)/g.test(line); + var hasELSE = /#else/g.test(line); + var hasENDIF = /#endif/g.test(line); + + if (hasIF) { + stack.push(line); + } else if (hasELSE) { + var top = stack[stack.length - 1]; + var op = top.replace('ifdef', 'ifndef'); + if (/if/g.test(op)) { + op = op.replace(/(#if\s+)(\S*)([^]*)/, '$1!($2)$3'); + } + stack.pop(); + stack.push(op); + } else if (hasENDIF) { + stack.pop(); + } else if (!/layout/g.test(line)) { + for (var varIndex = 0; varIndex < numLayoutVariables; ++varIndex) { + var varName = layoutVariables[varIndex]; + if (line.indexOf(varName) !== -1) { + if (!defined(variableMap[varName])) { + variableMap[varName] = stack.slice(); + } else { + variableMap[varName] = variableMap[varName].filter(function(x) { + return stack.indexOf(x) >= 0; + }); + } + } + } + } + } + + return variableMap; + } + + function removeExtension(name, splitSource) { + var regex = '#extension\\s+GL_' + name + '\\s+:\\s+[a-zA-Z0-9]+\\s*$'; + replaceInSourceRegex(new RegExp(regex, 'g'), '', splitSource); + } + + return modernizeShader; +}); diff --git a/Source/Scene/BlendEquation.js b/Source/Scene/BlendEquation.js index 27bf019d21eb..e589beeae418 100644 --- a/Source/Scene/BlendEquation.js +++ b/Source/Scene/BlendEquation.js @@ -34,9 +34,27 @@ define([ * @type {Number} * @constant */ - REVERSE_SUBTRACT : WebGLConstants.FUNC_REVERSE_SUBTRACT + REVERSE_SUBTRACT : WebGLConstants.FUNC_REVERSE_SUBTRACT, - // No min and max like in ColladaFX GLES2 profile + /** + * Pixel values are given to the minimum function (min(source, destination)). + * + * This equation operates on each pixel color component. + * + * @type {Number} + * @constant + */ + MIN : WebGLConstants.MIN, + + /** + * Pixel values are given to the maximum function (max(source, destination)). + * + * This equation operates on each pixel color component. + * + * @type {Number} + * @constant + */ + MAX : WebGLConstants.MAX }; return freezeObject(BlendEquation); diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl index c36fc9737286..34f8bc02c4ca 100644 --- a/Source/Shaders/GlobeFS.glsl +++ b/Source/Shaders/GlobeFS.glsl @@ -66,7 +66,7 @@ varying vec3 v_mieColor; vec4 sampleAndBlend( vec4 previousColor, - sampler2D texture, + sampler2D textureToSample, vec2 tileTextureCoordinates, vec4 textureCoordinateRectangle, vec4 textureCoordinateTranslationAndScale, @@ -94,7 +94,7 @@ vec4 sampleAndBlend( vec2 translation = textureCoordinateTranslationAndScale.xy; vec2 scale = textureCoordinateTranslationAndScale.zw; vec2 textureCoordinates = tileTextureCoordinates * scale + translation; - vec4 value = texture2D(texture, textureCoordinates); + vec4 value = texture2D(textureToSample, textureCoordinates); vec3 color = value.rgb; float alpha = value.a; diff --git a/Specs/Renderer/ShaderProgramSpec.js b/Specs/Renderer/ShaderProgramSpec.js index e2934ed94b66..0fe33e0acc0b 100644 --- a/Specs/Renderer/ShaderProgramSpec.js +++ b/Specs/Renderer/ShaderProgramSpec.js @@ -65,13 +65,13 @@ defineSuite([ var expectedVSText = new ShaderSource({ sources : [vs] - }).createCombinedVertexShader(); + }).createCombinedVertexShader(context); expect(sp._vertexShaderText).toEqual(expectedVSText); var expectedFSText = new ShaderSource({ sources : [fs] - }).createCombinedFragmentShader(); + }).createCombinedFragmentShader(context); expect(sp._fragmentShaderText).toEqual(expectedFSText); }); diff --git a/Specs/Renderer/ShaderSourceSpec.js b/Specs/Renderer/ShaderSourceSpec.js index b9105e4b28ed..80b4f4fbe9c4 100644 --- a/Specs/Renderer/ShaderSourceSpec.js +++ b/Specs/Renderer/ShaderSourceSpec.js @@ -4,12 +4,16 @@ defineSuite([ ShaderSource) { 'use strict'; + var mockContext = { + webgl2 : false + }; + it('combines #defines', function() { var source = new ShaderSource({ defines : ['A', 'B', ''] }); - var shaderText = source.createCombinedVertexShader(); + var shaderText = source.createCombinedVertexShader(mockContext); expect(shaderText).toContain('#define A'); expect(shaderText).toContain('#define B'); expect(shaderText.match(/#define/g).length).toEqual(2); @@ -19,7 +23,7 @@ defineSuite([ var source = new ShaderSource({ sources : ['void func() {}', 'void main() {}'] }); - var shaderText = source.createCombinedVertexShader(); + var shaderText = source.createCombinedVertexShader(mockContext); expect(shaderText).toContain('#line 0\nvoid func() {}'); expect(shaderText).toContain('#line 0\nvoid main() {}'); }); @@ -29,7 +33,7 @@ defineSuite([ defines : ['A', 'B', ''], sources : ['void func() {}', 'void main() {}'] }); - var shaderText = source.createCombinedVertexShader(); + var shaderText = source.createCombinedVertexShader(mockContext); expect(shaderText).toContain('#define A'); expect(shaderText).toContain('#define B'); expect(shaderText.match(/#define/g).length).toEqual(2); @@ -42,7 +46,7 @@ defineSuite([ sources : ['void main() { gl_FragColor = vec4(1.0); }'], pickColorQualifier : 'uniform' }); - var shaderText = source.createCombinedVertexShader(); + var shaderText = source.createCombinedVertexShader(mockContext); expect(shaderText).toContain('uniform vec4 czm_pickColor;'); expect(shaderText).toContain('gl_FragColor = czm_pickColor;'); }); @@ -52,7 +56,7 @@ defineSuite([ sources : ['void main() { gl_FragColor = vec4(1.0); }'], pickColorQualifier : 'varying' }); - var shaderText = source.createCombinedVertexShader(); + var shaderText = source.createCombinedVertexShader(mockContext); expect(shaderText).toContain('varying vec4 czm_pickColor;'); expect(shaderText).toContain('gl_FragColor = czm_pickColor;'); }); @@ -69,7 +73,7 @@ defineSuite([ var source = new ShaderSource({ sources : ['#version 300 es\nvoid main() {gl_FragColor = vec4(1.0); }'] }); - var shaderText = source.createCombinedVertexShader(); + var shaderText = source.createCombinedVertexShader(mockContext); expect(shaderText).toStartWith('#version 300 es\n'); }); }); diff --git a/Specs/Renderer/modernizeShaderSpec.js b/Specs/Renderer/modernizeShaderSpec.js new file mode 100644 index 000000000000..b915fd6461e7 --- /dev/null +++ b/Specs/Renderer/modernizeShaderSpec.js @@ -0,0 +1,317 @@ +defineSuite([ + 'Renderer/modernizeShader' + ], function( + modernizeShader) { + 'use strict'; + + it('adds version string', function() { + var simple = + '#define OUTPUT_DECLARATION \n' + + 'void main() \n' + + '{ \n' + + '} \n'; + var output = modernizeShader(simple, true); + var expected = '#version 300 es'; + expect(output.split('\n')[0]).toEqual(expected); + }); + + it('removes extensions', function() { + var extensions = + '#define OUTPUT_DECLARATION \n' + + '#extension GL_EXT_draw_buffers : enable \n' + + 'void main() \n' + + '{ \n' + + '} \n'; + var output = modernizeShader(extensions, true); + var notExpected = '#extension GL_EXT_draw_buffers : enable \n'; + expect(output).not.toContain(notExpected); + }); + + it('throws exception if no output declaration', function() { + var noOutputDeclaration = + 'void main() \n' + + '{ \n' + + '} \n'; + var runFunc = function() { + modernizeShader(noOutputDeclaration, true); + }; + expect(runFunc).toThrowDeveloperError(); + }); + + it('creates layout qualifier for gl_FragColor', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + 'void main() \n' + + '{ \n' + + ' gl_FragColor = vec4(0.0); \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expected = 'layout(location = 0) out vec4 czm_fragColor;'; + expect(output).toContain(expected); + }); + + it('creates layout qualifier for gl_FragData', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + 'void main() \n' + + '{ \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expected = 'layout(location = 0) out vec4 czm_out0;'; + expect(output).toContain(expected); + }); + + it('creates layout qualifier for MRT', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#extension GL_EXT_draw_buffers : enable \n' + + 'void main() \n' + + '{ \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' gl_FragData[1] = vec4(1.0); \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expected0 = 'layout(location = 0) out vec4 czm_out0;'; + var expected1 = 'layout(location = 1) out vec4 czm_out1;'; + expect(output).toContain(expected0); + expect(output).toContain(expected1); + }); + + it('does not create layout qualifier for reserved word lookalike variables', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + 'uniform sampler2D example; \n' + + 'void main() \n' + + '{ \n' + + ' vec4 gl_FragData_ = vec4(0.0); \n' + + ' vec4 a_gl_FragData = vec4(0.0); \n' + + ' vec2 thisIsNotAGoodNameForAtexture2D = vec2(0.0); \n' + + ' vec4 gl_FragColor = texture2D(example, thisIsNotAGoodNameForAtexture2D); \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expectedBadName = 'vec2 thisIsNotAGoodNameForAtexture2D'; + var expectedVariable = 'vec4 a_gl_FragData = vec4(0.0);'; + var expectedTextureCall = 'texture(example, thisIsNotAGoodNameForAtexture2D)'; + var notExpectedLayout = 'layout(location = 0) out czm_out'; + expect(output).toContain(expectedBadName); + expect(output).toContain(expectedVariable); + expect(output).toContain(expectedTextureCall); + expect(output).not.toContain(notExpectedLayout); + }); + + it('creates layout qualifier with swizzle', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#extension GL_EXT_draw_buffers : enable \n' + + 'void main() \n' + + '{ \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' gl_FragData[1].xyz = vec3(1.0); \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expected0 = 'layout(location = 0) out vec4 czm_out0;'; + var expected1 = 'layout(location = 1) out vec4 czm_out1;'; + expect(output).toContain(expected0); + expect(output).toContain(expected1); + }); + + it('removes old functions/variables from fragment shader', function() { + var old_fragment = + '#define OUTPUT_DECLARATION \n' + + '#extension GL_EXT_draw_buffers : enable \n' + + 'uniform sampler2D example; \n' + + 'uniform sampler2D exampleCube; \n' + + 'uniform sampler2D example3D; \n' + + 'varying vec2 v_textureCoordinates; \n' + + 'void main() \n' + + '{ \n' + + ' gl_FragData[0] = texture2D(example, v_textureCoordinates); \n' + + ' gl_FragData[1] = textureCube(exampleCube, v_textureCoordinates); \n' + + ' gl_FragData[2] = texture3D(example3D, v_textureCoordinates); \n' + + ' gl_FragDepthEXT = 0.0; \n' + + '} \n'; + + var output = modernizeShader(old_fragment, true); + + var expectedDepth = 'gl_FragDepth = 0.0;'; + var expectedTexture2D = 'texture(example, v_textureCoordinates);'; + var expectedTextureCube = 'texture(exampleCube, v_textureCoordinates);'; + var expectedTexture3D = 'texture(example3D, v_textureCoordinates);'; + var expectedIn = 'in vec2 v_textureCoordinates;'; + + var notExpectedDepth = 'gl_FragDepthEXT = 0.0;'; + var notExpectedTexture2D = 'texture2D(example, v_textureCoordinates);'; + var notExpectedTextureCube = 'textureCube(exampleCube, v_textureCoordinates);'; + var notExpectedTexture3D = 'texture3D(example3D, v_textureCoordinates);'; + var notExpectedVarying = 'varying vec2 v_textureCoordinates;'; + + expect(output).toContain(expectedDepth); + expect(output).toContain(expectedTexture2D); + expect(output).toContain(expectedTextureCube); + expect(output).toContain(expectedTexture3D); + expect(output).toContain(expectedIn); + + expect(output).not.toContain(notExpectedDepth); + expect(output).not.toContain(notExpectedTexture2D); + expect(output).not.toContain(notExpectedTextureCube); + expect(output).not.toContain(notExpectedTexture3D); + expect(output).not.toContain(notExpectedVarying); + }); + + it('removes old functions/variables from vertex shader', function() { + var old_vertex = + '#define OUTPUT_DECLARATION \n' + + 'attribute vec4 position; \n' + + 'varying vec4 varyingVariable; \n' + + 'void main() \n' + + '{ \n' + + ' gl_Position = position; \n' + + ' varyingVariable = position.wzyx; \n' + + '} \n'; + + var output = modernizeShader(old_vertex, false); + + var expectedOut = 'out vec4 varyingVariable;'; + var expectedIn = 'in vec4 position;'; + + var notExpectedAttribute = 'attribute vec4 varyingVariable;'; + var notExpectedVarying = 'varying vec2 varyingVariable;'; + + expect(output).toContain(expectedOut); + expect(output).toContain(expectedIn); + + expect(output).not.toContain(notExpectedAttribute); + expect(output).not.toContain(notExpectedVarying); + }); + + it('creates single layout qualifier under single branch, single condition', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#define EXAMPLE_BRANCH \n' + + 'void main() \n' + + '{ \n' + + ' #ifdef EXAMPLE_BRANCH \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' #endif //EXAMPLE_BRANCH \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expected = '#ifdef EXAMPLE_BRANCH \nlayout(location = 0) out vec4 czm_out0;\n#endif'; + expect(output).toContain(expected); + }); + + it('creates multiple layout qualifiers under single branch, single condition', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#define EXAMPLE_BRANCH \n' + + 'void main() \n' + + '{ \n' + + ' #ifdef EXAMPLE_BRANCH \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' gl_FragData[1] = vec4(1.0); \n' + + ' #endif //EXAMPLE_BRANCH \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expected0 = '#ifdef EXAMPLE_BRANCH \nlayout(location = 0) out vec4 czm_out0;\n#endif'; + var expected1 = '#ifdef EXAMPLE_BRANCH \nlayout(location = 1) out vec4 czm_out1;\n#endif'; + expect(output).toContain(expected0); + expect(output).toContain(expected1); + }); + + it('creates multiple layout qualifiers under multiple branches, single condition (cancels)', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#define EXAMPLE_BRANCH \n' + + 'void main() \n' + + '{ \n' + + ' #ifdef EXAMPLE_BRANCH \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' gl_FragData[1] = vec4(1.0); \n' + + ' #endif //EXAMPLE_BRANCH \n' + + ' #ifndef EXAMPLE_BRANCH \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' gl_FragData[1] = vec4(1.0); \n' + + ' #endif //!EXAMPLE_BRANCH \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var notExpected = '#ifdef EXAMPLE_BRANCH \nlayout(location = 0) out vec4 czm_out0;\n#endif'; + expect(output).not.toContain(notExpected); + }); + + it('creates single layout qualifier under multiple branches, multiple conditions (cancels)', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#define EXAMPLE_BRANCH \n' + + '#define EXAMPLE_BRANCH1 \n' + + 'void main() \n' + + '{ \n' + + ' #ifdef EXAMPLE_BRANCH \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' #endif //EXAMPLE_BRANCH \n' + + ' #ifdef EXAMPLE_BRANCH1 \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' #endif //EXAMPLE_BRANCH1 \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var notExpected = '#ifdef EXAMPLE_BRANCH \nlayout(location = 0) out vec4 czm_out0;\n#endif'; + expect(output).not.toContain(notExpected); + }); + + it('creates multiple layout qualifiers under multiple branches, multiple conditions (cascades)', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#define EXAMPLE_BRANCH \n' + + '#define EXAMPLE_BRANCH1 \n' + + 'void main() \n' + + '{ \n' + + ' #ifdef EXAMPLE_BRANCH \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' #ifdef EXAMPLE_BRANCH1 \n' + + ' gl_FragData[1] = vec4(1.0); \n' + + ' #endif //EXAMPLE_BRANCH1 \n' + + ' #endif //EXAMPLE_BRANCH \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expected0 = '#ifdef EXAMPLE_BRANCH \nlayout(location = 0) out vec4 czm_out0;\n#endif'; + var expected1 = /#ifdef (EXAMPLE_BRANCH|EXAMPLE_BRANCH1)\s*\n\s*#ifdef (EXAMPLE_BRANCH1|EXAMPLE_BRANCH)\s*\n\s*layout\(location = 1\) out vec4 czm_out1;/g; + var containsExpected0 = expected1.test(output); + expect(output).toContain(expected0); + expect(containsExpected0).toBe(true); + }); + + it('creates single layout qualifier under multiple branches, single condition (else)', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#define EXAMPLE_BRANCH \n' + + 'void main() \n' + + '{ \n' + + ' #ifdef EXAMPLE_BRANCH \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' #else \n' + + ' gl_FragData[0] = vec4(1.0); \n' + + ' #endif //EXAMPLE_BRANCH \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var notExpected = '#ifdef EXAMPLE_BRANCH \nlayout(location = 0) out vec4 czm_out0;\n#endif'; + expect(output).not.toContain(notExpected); + }); + + it('creates branched layout qualifiers for gl_FragColor and gl_FragData', function() { + var noQualifiers = + '#define OUTPUT_DECLARATION \n' + + '#define EXAMPLE_BRANCH \n' + + 'void main() \n' + + '{ \n' + + ' #ifdef EXAMPLE_BRANCH \n' + + ' gl_FragData[0] = vec4(0.0); \n' + + ' #else \n' + + ' gl_FragColor = vec4(1.0); \n' + + ' #endif //EXAMPLE_BRANCH \n' + + '} \n'; + var output = modernizeShader(noQualifiers, true); + var expected0 = '#ifdef EXAMPLE_BRANCH \nlayout(location = 0) out vec4 czm_out0;\n#endif'; + var expected1 = '#ifndef EXAMPLE_BRANCH \nlayout(location = 0) out vec4 czm_fragColor;\n#endif'; + expect(output).toContain(expected0); + expect(output).toContain(expected1); + }); +});