ShaderSource.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. import defaultValue from '../Core/defaultValue.js';
  2. import defined from '../Core/defined.js';
  3. import DeveloperError from '../Core/DeveloperError.js';
  4. import modernizeShader from '../Renderer/modernizeShader.js';
  5. import CzmBuiltins from '../Shaders/Builtin/CzmBuiltins.js';
  6. import AutomaticUniforms from './AutomaticUniforms.js';
  7. function removeComments(source) {
  8. // remove inline comments
  9. source = source.replace(/\/\/.*/g, '');
  10. // remove multiline comment block
  11. return source.replace(/\/\*\*[\s\S]*?\*\//gm, function(match) {
  12. // preserve the number of lines in the comment block so the line numbers will be correct when debugging shaders
  13. var numberOfLines = match.match(/\n/gm).length;
  14. var replacement = '';
  15. for (var lineNumber = 0; lineNumber < numberOfLines; ++lineNumber) {
  16. replacement += '\n';
  17. }
  18. return replacement;
  19. });
  20. }
  21. function getDependencyNode(name, glslSource, nodes) {
  22. var dependencyNode;
  23. // check if already loaded
  24. for (var i = 0; i < nodes.length; ++i) {
  25. if (nodes[i].name === name) {
  26. dependencyNode = nodes[i];
  27. }
  28. }
  29. if (!defined(dependencyNode)) {
  30. // strip doc comments so we don't accidentally try to determine a dependency for something found
  31. // in a comment
  32. glslSource = removeComments(glslSource);
  33. // create new node
  34. dependencyNode = {
  35. name : name,
  36. glslSource : glslSource,
  37. dependsOn : [],
  38. requiredBy : [],
  39. evaluated : false
  40. };
  41. nodes.push(dependencyNode);
  42. }
  43. return dependencyNode;
  44. }
  45. function generateDependencies(currentNode, dependencyNodes) {
  46. if (currentNode.evaluated) {
  47. return;
  48. }
  49. currentNode.evaluated = true;
  50. // identify all dependencies that are referenced from this glsl source code
  51. var czmMatches = currentNode.glslSource.match(/\bczm_[a-zA-Z0-9_]*/g);
  52. if (defined(czmMatches) && czmMatches !== null) {
  53. // remove duplicates
  54. czmMatches = czmMatches.filter(function(elem, pos) {
  55. return czmMatches.indexOf(elem) === pos;
  56. });
  57. czmMatches.forEach(function(element) {
  58. if (element !== currentNode.name && ShaderSource._czmBuiltinsAndUniforms.hasOwnProperty(element)) {
  59. var referencedNode = getDependencyNode(element, ShaderSource._czmBuiltinsAndUniforms[element], dependencyNodes);
  60. currentNode.dependsOn.push(referencedNode);
  61. referencedNode.requiredBy.push(currentNode);
  62. // recursive call to find any dependencies of the new node
  63. generateDependencies(referencedNode, dependencyNodes);
  64. }
  65. });
  66. }
  67. }
  68. function sortDependencies(dependencyNodes) {
  69. var nodesWithoutIncomingEdges = [];
  70. var allNodes = [];
  71. while (dependencyNodes.length > 0) {
  72. var node = dependencyNodes.pop();
  73. allNodes.push(node);
  74. if (node.requiredBy.length === 0) {
  75. nodesWithoutIncomingEdges.push(node);
  76. }
  77. }
  78. while (nodesWithoutIncomingEdges.length > 0) {
  79. var currentNode = nodesWithoutIncomingEdges.shift();
  80. dependencyNodes.push(currentNode);
  81. for (var i = 0; i < currentNode.dependsOn.length; ++i) {
  82. // remove the edge from the graph
  83. var referencedNode = currentNode.dependsOn[i];
  84. var index = referencedNode.requiredBy.indexOf(currentNode);
  85. referencedNode.requiredBy.splice(index, 1);
  86. // if referenced node has no more incoming edges, add to list
  87. if (referencedNode.requiredBy.length === 0) {
  88. nodesWithoutIncomingEdges.push(referencedNode);
  89. }
  90. }
  91. }
  92. // if there are any nodes left with incoming edges, then there was a circular dependency somewhere in the graph
  93. var badNodes = [];
  94. for (var j = 0; j < allNodes.length; ++j) {
  95. if (allNodes[j].requiredBy.length !== 0) {
  96. badNodes.push(allNodes[j]);
  97. }
  98. }
  99. //>>includeStart('debug', pragmas.debug);
  100. if (badNodes.length !== 0) {
  101. var message = 'A circular dependency was found in the following built-in functions/structs/constants: \n';
  102. for (var k = 0; k < badNodes.length; ++k) {
  103. message = message + badNodes[k].name + '\n';
  104. }
  105. throw new DeveloperError(message);
  106. }
  107. //>>includeEnd('debug');
  108. }
  109. function getBuiltinsAndAutomaticUniforms(shaderSource) {
  110. // generate a dependency graph for builtin functions
  111. var dependencyNodes = [];
  112. var root = getDependencyNode('main', shaderSource, dependencyNodes);
  113. generateDependencies(root, dependencyNodes);
  114. sortDependencies(dependencyNodes);
  115. // Concatenate the source code for the function dependencies.
  116. // Iterate in reverse so that dependent items are declared before they are used.
  117. var builtinsSource = '';
  118. for (var i = dependencyNodes.length - 1; i >= 0; --i) {
  119. builtinsSource = builtinsSource + dependencyNodes[i].glslSource + '\n';
  120. }
  121. return builtinsSource.replace(root.glslSource, '');
  122. }
  123. function combineShader(shaderSource, isFragmentShader, context) {
  124. var i;
  125. var length;
  126. // Combine shader sources, generally for pseudo-polymorphism, e.g., czm_getMaterial.
  127. var combinedSources = '';
  128. var sources = shaderSource.sources;
  129. if (defined(sources)) {
  130. for (i = 0, length = sources.length; i < length; ++i) {
  131. // #line needs to be on its own line.
  132. combinedSources += '\n#line 0\n' + sources[i];
  133. }
  134. }
  135. combinedSources = removeComments(combinedSources);
  136. // Extract existing shader version from sources
  137. var version;
  138. combinedSources = combinedSources.replace(/#version\s+(.*?)\n/gm, function(match, group1) {
  139. //>>includeStart('debug', pragmas.debug);
  140. if (defined(version) && version !== group1) {
  141. throw new DeveloperError('inconsistent versions found: ' + version + ' and ' + group1);
  142. }
  143. //>>includeEnd('debug');
  144. // Extract #version to put at the top
  145. version = group1;
  146. // Replace original #version directive with a new line so the line numbers
  147. // are not off by one. There can be only one #version directive
  148. // and it must appear at the top of the source, only preceded by
  149. // whitespace and comments.
  150. return '\n';
  151. });
  152. // Extract shader extensions from sources
  153. var extensions = [];
  154. combinedSources = combinedSources.replace(/#extension.*\n/gm, function(match) {
  155. // Extract extension to put at the top
  156. extensions.push(match);
  157. // Replace original #extension directive with a new line so the line numbers
  158. // are not off by one.
  159. return '\n';
  160. });
  161. // Remove precision qualifier
  162. combinedSources = combinedSources.replace(/precision\s(lowp|mediump|highp)\s(float|int);/, '');
  163. // Replace main() for picked if desired.
  164. var pickColorQualifier = shaderSource.pickColorQualifier;
  165. if (defined(pickColorQualifier)) {
  166. combinedSources = ShaderSource.createPickFragmentShaderSource(combinedSources, pickColorQualifier);
  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 + '\n';
  174. }
  175. var extensionsLength = extensions.length;
  176. for (i = 0; i < extensionsLength; i++) {
  177. result += extensions[i];
  178. }
  179. if (isFragmentShader) {
  180. result += '\
  181. #ifdef GL_FRAGMENT_PRECISION_HIGH\n\
  182. precision highp float;\n\
  183. #else\n\
  184. precision mediump float;\n\
  185. #endif\n\n';
  186. }
  187. // Prepend #defines for uber-shaders
  188. var defines = shaderSource.defines;
  189. if (defined(defines)) {
  190. for (i = 0, length = defines.length; i < length; ++i) {
  191. var define = defines[i];
  192. if (define.length !== 0) {
  193. result += '#define ' + define + '\n';
  194. }
  195. }
  196. }
  197. // GLSLModernizer inserts its own layout qualifiers
  198. // at this position in the source
  199. if (context.webgl2) {
  200. result += '#define OUTPUT_DECLARATION\n\n';
  201. }
  202. // Define a constant for the OES_texture_float_linear extension since WebGL does not.
  203. if (context.textureFloatLinear) {
  204. result += '#define OES_texture_float_linear\n\n';
  205. }
  206. // append built-ins
  207. if (shaderSource.includeBuiltIns) {
  208. result += getBuiltinsAndAutomaticUniforms(combinedSources);
  209. }
  210. // reset line number
  211. result += '\n#line 0\n';
  212. // append actual source
  213. result += combinedSources;
  214. // modernize the source
  215. if (context.webgl2) {
  216. result = modernizeShader(result, isFragmentShader, true);
  217. }
  218. return result;
  219. }
  220. /**
  221. * An object containing various inputs that will be combined to form a final GLSL shader string.
  222. *
  223. * @param {Object} [options] Object with the following properties:
  224. * @param {String[]} [options.sources] An array of strings to combine containing GLSL code for the shader.
  225. * @param {String[]} [options.defines] An array of strings containing GLSL identifiers to <code>#define</code>.
  226. * @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.
  227. * @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.
  228. *
  229. * @exception {DeveloperError} options.pickColorQualifier must be 'uniform' or 'varying'.
  230. *
  231. * @example
  232. * // 1. Prepend #defines to a shader
  233. * var source = new Cesium.ShaderSource({
  234. * defines : ['WHITE'],
  235. * sources : ['void main() { \n#ifdef WHITE\n gl_FragColor = vec4(1.0); \n#else\n gl_FragColor = vec4(0.0); \n#endif\n }']
  236. * });
  237. *
  238. * // 2. Modify a fragment shader for picking
  239. * var source = new Cesium.ShaderSource({
  240. * sources : ['void main() { gl_FragColor = vec4(1.0); }'],
  241. * pickColorQualifier : 'uniform'
  242. * });
  243. *
  244. * @private
  245. */
  246. function ShaderSource(options) {
  247. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  248. var pickColorQualifier = options.pickColorQualifier;
  249. //>>includeStart('debug', pragmas.debug);
  250. if (defined(pickColorQualifier) && pickColorQualifier !== 'uniform' && pickColorQualifier !== 'varying') {
  251. throw new DeveloperError('options.pickColorQualifier must be \'uniform\' or \'varying\'.');
  252. }
  253. //>>includeEnd('debug');
  254. this.defines = defined(options.defines) ? options.defines.slice(0) : [];
  255. this.sources = defined(options.sources) ? options.sources.slice(0) : [];
  256. this.pickColorQualifier = pickColorQualifier;
  257. this.includeBuiltIns = defaultValue(options.includeBuiltIns, true);
  258. }
  259. ShaderSource.prototype.clone = function() {
  260. return new ShaderSource({
  261. sources : this.sources,
  262. defines : this.defines,
  263. pickColorQualifier : this.pickColorQualifier,
  264. includeBuiltIns : this.includeBuiltIns
  265. });
  266. };
  267. ShaderSource.replaceMain = function(source, renamedMain) {
  268. renamedMain = 'void ' + renamedMain + '()';
  269. return source.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, renamedMain);
  270. };
  271. /**
  272. * Create a single string containing the full, combined vertex shader with all dependencies and defines.
  273. *
  274. * @param {Context} context The current rendering context
  275. *
  276. * @returns {String} The combined shader string.
  277. */
  278. ShaderSource.prototype.createCombinedVertexShader = function(context) {
  279. return combineShader(this, false, context);
  280. };
  281. /**
  282. * Create a single string containing the full, combined fragment shader with all dependencies and defines.
  283. *
  284. * @param {Context} context The current rendering context
  285. *
  286. * @returns {String} The combined shader string.
  287. */
  288. ShaderSource.prototype.createCombinedFragmentShader = function(context) {
  289. return combineShader(this, true, context);
  290. };
  291. /**
  292. * For ShaderProgram testing
  293. * @private
  294. */
  295. ShaderSource._czmBuiltinsAndUniforms = {};
  296. // combine automatic uniforms and Cesium built-ins
  297. for ( var builtinName in CzmBuiltins) {
  298. if (CzmBuiltins.hasOwnProperty(builtinName)) {
  299. ShaderSource._czmBuiltinsAndUniforms[builtinName] = CzmBuiltins[builtinName];
  300. }
  301. }
  302. for ( var uniformName in AutomaticUniforms) {
  303. if (AutomaticUniforms.hasOwnProperty(uniformName)) {
  304. var uniform = AutomaticUniforms[uniformName];
  305. if (typeof uniform.getDeclaration === 'function') {
  306. ShaderSource._czmBuiltinsAndUniforms[uniformName] = uniform.getDeclaration(uniformName);
  307. }
  308. }
  309. }
  310. ShaderSource.createPickVertexShaderSource = function(vertexShaderSource) {
  311. var renamedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_old_main');
  312. var pickMain = 'attribute vec4 pickColor; \n' +
  313. 'varying vec4 czm_pickColor; \n' +
  314. 'void main() \n' +
  315. '{ \n' +
  316. ' czm_old_main(); \n' +
  317. ' czm_pickColor = pickColor; \n' +
  318. '}';
  319. return renamedVS + '\n' + pickMain;
  320. };
  321. ShaderSource.createPickFragmentShaderSource = function(fragmentShaderSource, pickColorQualifier) {
  322. var renamedFS = ShaderSource.replaceMain(fragmentShaderSource, 'czm_old_main');
  323. var pickMain = pickColorQualifier + ' vec4 czm_pickColor; \n' +
  324. 'void main() \n' +
  325. '{ \n' +
  326. ' czm_old_main(); \n' +
  327. ' if (gl_FragColor.a == 0.0) { \n' +
  328. ' discard; \n' +
  329. ' } \n' +
  330. ' gl_FragColor = czm_pickColor; \n' +
  331. '}';
  332. return renamedFS + '\n' + pickMain;
  333. };
  334. ShaderSource.findVarying = function(shaderSource, names) {
  335. var sources = shaderSource.sources;
  336. var namesLength = names.length;
  337. for (var i = 0; i < namesLength; ++i) {
  338. var name = names[i];
  339. var sourcesLength = sources.length;
  340. for (var j = 0; j < sourcesLength; ++j) {
  341. if (sources[j].indexOf(name) !== -1) {
  342. return name;
  343. }
  344. }
  345. }
  346. return undefined;
  347. };
  348. var normalVaryingNames = ['v_normalEC', 'v_normal'];
  349. ShaderSource.findNormalVarying = function(shaderSource) {
  350. return ShaderSource.findVarying(shaderSource, normalVaryingNames);
  351. };
  352. var positionVaryingNames = ['v_positionEC'];
  353. ShaderSource.findPositionVarying = function(shaderSource) {
  354. return ShaderSource.findVarying(shaderSource, positionVaryingNames);
  355. };
  356. export default ShaderSource;