ShaderSource.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /*global define*/
  2. define([
  3. '../Core/defaultValue',
  4. '../Core/defined',
  5. '../Core/DeveloperError',
  6. '../Shaders/Builtin/CzmBuiltins',
  7. './AutomaticUniforms'
  8. ], function(
  9. defaultValue,
  10. defined,
  11. DeveloperError,
  12. CzmBuiltins,
  13. AutomaticUniforms) {
  14. "use strict";
  15. function removeComments(source) {
  16. return source.replace(/\/\*\*[\s\S]*?\*\//gm, function(match) {
  17. // preserve the number of lines in the comment block so the line numbers will be correct when debugging shaders
  18. var numberOfLines = match.match(/\n/gm).length;
  19. var replacement = '';
  20. for (var lineNumber = 0; lineNumber < numberOfLines; ++lineNumber) {
  21. replacement += '\n';
  22. }
  23. return replacement;
  24. });
  25. }
  26. function getDependencyNode(name, glslSource, nodes) {
  27. var dependencyNode;
  28. // check if already loaded
  29. for (var i = 0; i < nodes.length; ++i) {
  30. if (nodes[i].name === name) {
  31. dependencyNode = nodes[i];
  32. }
  33. }
  34. if (!defined(dependencyNode)) {
  35. // strip doc comments so we don't accidentally try to determine a dependency for something found
  36. // in a comment
  37. glslSource = removeComments(glslSource);
  38. // create new node
  39. dependencyNode = {
  40. name : name,
  41. glslSource : glslSource,
  42. dependsOn : [],
  43. requiredBy : [],
  44. evaluated : false
  45. };
  46. nodes.push(dependencyNode);
  47. }
  48. return dependencyNode;
  49. }
  50. function generateDependencies(currentNode, dependencyNodes) {
  51. if (currentNode.evaluated) {
  52. return;
  53. }
  54. currentNode.evaluated = true;
  55. // identify all dependencies that are referenced from this glsl source code
  56. var czmMatches = currentNode.glslSource.match(/\bczm_[a-zA-Z0-9_]*/g);
  57. if (defined(czmMatches) && czmMatches !== null) {
  58. // remove duplicates
  59. czmMatches = czmMatches.filter(function(elem, pos) {
  60. return czmMatches.indexOf(elem) === pos;
  61. });
  62. czmMatches.forEach(function(element) {
  63. if (element !== currentNode.name && ShaderSource._czmBuiltinsAndUniforms.hasOwnProperty(element)) {
  64. var referencedNode = getDependencyNode(element, ShaderSource._czmBuiltinsAndUniforms[element], dependencyNodes);
  65. currentNode.dependsOn.push(referencedNode);
  66. referencedNode.requiredBy.push(currentNode);
  67. // recursive call to find any dependencies of the new node
  68. generateDependencies(referencedNode, dependencyNodes);
  69. }
  70. });
  71. }
  72. }
  73. function sortDependencies(dependencyNodes) {
  74. var nodesWithoutIncomingEdges = [];
  75. var allNodes = [];
  76. while (dependencyNodes.length > 0) {
  77. var node = dependencyNodes.pop();
  78. allNodes.push(node);
  79. if (node.requiredBy.length === 0) {
  80. nodesWithoutIncomingEdges.push(node);
  81. }
  82. }
  83. while (nodesWithoutIncomingEdges.length > 0) {
  84. var currentNode = nodesWithoutIncomingEdges.shift();
  85. dependencyNodes.push(currentNode);
  86. for (var i = 0; i < currentNode.dependsOn.length; ++i) {
  87. // remove the edge from the graph
  88. var referencedNode = currentNode.dependsOn[i];
  89. var index = referencedNode.requiredBy.indexOf(currentNode);
  90. referencedNode.requiredBy.splice(index, 1);
  91. // if referenced node has no more incoming edges, add to list
  92. if (referencedNode.requiredBy.length === 0) {
  93. nodesWithoutIncomingEdges.push(referencedNode);
  94. }
  95. }
  96. }
  97. // if there are any nodes left with incoming edges, then there was a circular dependency somewhere in the graph
  98. var badNodes = [];
  99. for (var j = 0; j < allNodes.length; ++j) {
  100. if (allNodes[j].requiredBy.length !== 0) {
  101. badNodes.push(allNodes[j]);
  102. }
  103. }
  104. if (badNodes.length !== 0) {
  105. var message = 'A circular dependency was found in the following built-in functions/structs/constants: \n';
  106. for (j = 0; j < badNodes.length; ++j) {
  107. message = message + badNodes[j].name + '\n';
  108. }
  109. throw new DeveloperError(message);
  110. }
  111. }
  112. function getBuiltinsAndAutomaticUniforms(shaderSource) {
  113. // generate a dependency graph for builtin functions
  114. var dependencyNodes = [];
  115. var root = getDependencyNode('main', shaderSource, dependencyNodes);
  116. generateDependencies(root, dependencyNodes);
  117. sortDependencies(dependencyNodes);
  118. // Concatenate the source code for the function dependencies.
  119. // Iterate in reverse so that dependent items are declared before they are used.
  120. var builtinsSource = '';
  121. for (var i = dependencyNodes.length - 1; i >= 0; --i) {
  122. builtinsSource = builtinsSource + dependencyNodes[i].glslSource + '\n';
  123. }
  124. return builtinsSource.replace(root.glslSource, '');
  125. }
  126. function combineShader(shaderSource, isFragmentShader) {
  127. var i;
  128. var length;
  129. // Combine shader sources, generally for pseudo-polymorphism, e.g., czm_getMaterial.
  130. var combinedSources = '';
  131. var sources = shaderSource.sources;
  132. if (defined(sources)) {
  133. for (i = 0, length = sources.length; i < length; ++i) {
  134. // #line needs to be on its own line.
  135. combinedSources += '\n#line 0\n' + sources[i];
  136. }
  137. }
  138. combinedSources = removeComments(combinedSources);
  139. // Extract existing shader version from sources
  140. var version;
  141. combinedSources = combinedSources.replace(/#version\s+(.*?)\n/gm, function(match, group1) {
  142. if (defined(version) && version !== group1) {
  143. throw new DeveloperError('inconsistent versions found: ' + version + ' and ' + group1);
  144. }
  145. // Extract #version to put at the top
  146. version = group1;
  147. // Replace original #version directive with a new line so the line numbers
  148. // are not off by one. There can be only one #version directive
  149. // and it must appear at the top of the source, only preceded by
  150. // whitespace and comments.
  151. return '\n';
  152. });
  153. // Replace main() for picked if desired.
  154. var pickColorQualifier = shaderSource.pickColorQualifier;
  155. if (defined(pickColorQualifier)) {
  156. combinedSources = combinedSources.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, 'void czm_old_main()');
  157. combinedSources += '\
  158. \n' + pickColorQualifier + ' vec4 czm_pickColor;\n\
  159. void main()\n\
  160. {\n\
  161. czm_old_main();\n\
  162. if (gl_FragColor.a == 0.0) {\n\
  163. discard;\n\
  164. }\n\
  165. gl_FragColor = czm_pickColor;\n\
  166. }';
  167. }
  168. // combine into single string
  169. var result = '';
  170. // #version must be first
  171. // defaults to #version 100 if not specified
  172. if (defined(version)) {
  173. result = '#version ' + version;
  174. }
  175. if (isFragmentShader) {
  176. result += '\
  177. #ifdef GL_FRAGMENT_PRECISION_HIGH\n\
  178. precision highp float;\n\
  179. #else\n\
  180. precision mediump float;\n\
  181. #endif\n\n';
  182. }
  183. // Prepend #defines for uber-shaders
  184. var defines = shaderSource.defines;
  185. if (defined(defines)) {
  186. for (i = 0, length = defines.length; i < length; ++i) {
  187. var define = defines[i];
  188. if (define.length !== 0) {
  189. result += '#define ' + define + '\n';
  190. }
  191. }
  192. }
  193. // append built-ins
  194. if (shaderSource.includeBuiltIns) {
  195. result += getBuiltinsAndAutomaticUniforms(combinedSources);
  196. }
  197. // reset line number
  198. result += '\n#line 0\n';
  199. // append actual source
  200. result += combinedSources;
  201. return result;
  202. }
  203. /**
  204. * An object containing various inputs that will be combined to form a final GLSL shader string.
  205. *
  206. * @param {Object} [options] Object with the following properties:
  207. * @param {String[]} [options.sources] An array of strings to combine containing GLSL code for the shader.
  208. * @param {String[]} [options.defines] An array of strings containing GLSL identifiers to <code>#define</code>.
  209. * @param {String} [options.pickColorQualifier] The GLSL qualifier, <code>uniform</code> or <code>varying</code>, for the input <code>czm_pickColor</code>. When defined, a pick fragment shader is generated.
  210. * @param {Boolean} [options.includeBuiltIns=true] If true, referenced built-in functions will be included with the combined shader. Set to false if this shader will become a source in another shader, to avoid duplicating functions.
  211. *
  212. * @exception {DeveloperError} options.pickColorQualifier must be 'uniform' or 'varying'.
  213. *
  214. * @example
  215. * // 1. Prepend #defines to a shader
  216. * var source = new Cesium.ShaderSource({
  217. * defines : ['WHITE'],
  218. * sources : ['void main() { \n#ifdef WHITE\n gl_FragColor = vec4(1.0); \n#else\n gl_FragColor = vec4(0.0); \n#endif\n }']
  219. * });
  220. *
  221. * // 2. Modify a fragment shader for picking
  222. * var source = new Cesium.ShaderSource({
  223. * sources : ['void main() { gl_FragColor = vec4(1.0); }'],
  224. * pickColorQualifier : 'uniform'
  225. * });
  226. *
  227. * @private
  228. */
  229. var ShaderSource = function(options) {
  230. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  231. var pickColorQualifier = options.pickColorQualifier;
  232. //>>includeStart('debug', pragmas.debug);
  233. if (defined(pickColorQualifier) && pickColorQualifier !== 'uniform' && pickColorQualifier !== 'varying') {
  234. throw new DeveloperError('options.pickColorQualifier must be \'uniform\' or \'varying\'.');
  235. }
  236. //>>includeEnd('debug');
  237. this.defines = defined(options.defines) ? options.defines.slice(0) : [];
  238. this.sources = defined(options.sources) ? options.sources.slice(0) : [];
  239. this.pickColorQualifier = pickColorQualifier;
  240. this.includeBuiltIns = defaultValue(options.includeBuiltIns, true);
  241. };
  242. ShaderSource.prototype.clone = function() {
  243. return new ShaderSource({
  244. sources : this.sources,
  245. defines : this.defines,
  246. pickColorQuantifier : this.pickColorQualifier,
  247. includeBuiltIns : this.includeBuiltIns
  248. });
  249. };
  250. /**
  251. * Create a single string containing the full, combined vertex shader with all dependencies and defines.
  252. *
  253. * @returns {String} The combined shader string.
  254. */
  255. ShaderSource.prototype.createCombinedVertexShader = function() {
  256. return combineShader(this, false);
  257. };
  258. /**
  259. * Create a single string containing the full, combined fragment shader with all dependencies and defines.
  260. *
  261. * @returns {String} The combined shader string.
  262. */
  263. ShaderSource.prototype.createCombinedFragmentShader = function() {
  264. return combineShader(this, true);
  265. };
  266. /**
  267. * For ShaderProgram testing
  268. * @private
  269. */
  270. ShaderSource._czmBuiltinsAndUniforms = {};
  271. // combine automatic uniforms and Cesium built-ins
  272. for ( var builtinName in CzmBuiltins) {
  273. if (CzmBuiltins.hasOwnProperty(builtinName)) {
  274. ShaderSource._czmBuiltinsAndUniforms[builtinName] = CzmBuiltins[builtinName];
  275. }
  276. }
  277. for ( var uniformName in AutomaticUniforms) {
  278. if (AutomaticUniforms.hasOwnProperty(uniformName)) {
  279. var uniform = AutomaticUniforms[uniformName];
  280. if (typeof uniform.getDeclaration === 'function') {
  281. ShaderSource._czmBuiltinsAndUniforms[uniformName] = uniform.getDeclaration(uniformName);
  282. }
  283. }
  284. }
  285. return ShaderSource;
  286. });