ShaderSource.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  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 (
  59. element !== currentNode.name &&
  60. ShaderSource._czmBuiltinsAndUniforms.hasOwnProperty(element)
  61. ) {
  62. var referencedNode = getDependencyNode(
  63. element,
  64. ShaderSource._czmBuiltinsAndUniforms[element],
  65. dependencyNodes
  66. );
  67. currentNode.dependsOn.push(referencedNode);
  68. referencedNode.requiredBy.push(currentNode);
  69. // recursive call to find any dependencies of the new node
  70. generateDependencies(referencedNode, dependencyNodes);
  71. }
  72. });
  73. }
  74. }
  75. function sortDependencies(dependencyNodes) {
  76. var nodesWithoutIncomingEdges = [];
  77. var allNodes = [];
  78. while (dependencyNodes.length > 0) {
  79. var node = dependencyNodes.pop();
  80. allNodes.push(node);
  81. if (node.requiredBy.length === 0) {
  82. nodesWithoutIncomingEdges.push(node);
  83. }
  84. }
  85. while (nodesWithoutIncomingEdges.length > 0) {
  86. var currentNode = nodesWithoutIncomingEdges.shift();
  87. dependencyNodes.push(currentNode);
  88. for (var i = 0; i < currentNode.dependsOn.length; ++i) {
  89. // remove the edge from the graph
  90. var referencedNode = currentNode.dependsOn[i];
  91. var index = referencedNode.requiredBy.indexOf(currentNode);
  92. referencedNode.requiredBy.splice(index, 1);
  93. // if referenced node has no more incoming edges, add to list
  94. if (referencedNode.requiredBy.length === 0) {
  95. nodesWithoutIncomingEdges.push(referencedNode);
  96. }
  97. }
  98. }
  99. // if there are any nodes left with incoming edges, then there was a circular dependency somewhere in the graph
  100. var badNodes = [];
  101. for (var j = 0; j < allNodes.length; ++j) {
  102. if (allNodes[j].requiredBy.length !== 0) {
  103. badNodes.push(allNodes[j]);
  104. }
  105. }
  106. //>>includeStart('debug', pragmas.debug);
  107. if (badNodes.length !== 0) {
  108. var message =
  109. "A circular dependency was found in the following built-in functions/structs/constants: \n";
  110. for (var k = 0; k < badNodes.length; ++k) {
  111. message = message + badNodes[k].name + "\n";
  112. }
  113. throw new DeveloperError(message);
  114. }
  115. //>>includeEnd('debug');
  116. }
  117. function getBuiltinsAndAutomaticUniforms(shaderSource) {
  118. // generate a dependency graph for builtin functions
  119. var dependencyNodes = [];
  120. var root = getDependencyNode("main", shaderSource, dependencyNodes);
  121. generateDependencies(root, dependencyNodes);
  122. sortDependencies(dependencyNodes);
  123. // Concatenate the source code for the function dependencies.
  124. // Iterate in reverse so that dependent items are declared before they are used.
  125. var builtinsSource = "";
  126. for (var i = dependencyNodes.length - 1; i >= 0; --i) {
  127. builtinsSource = builtinsSource + dependencyNodes[i].glslSource + "\n";
  128. }
  129. return builtinsSource.replace(root.glslSource, "");
  130. }
  131. function combineShader(shaderSource, isFragmentShader, context) {
  132. var i;
  133. var length;
  134. // Combine shader sources, generally for pseudo-polymorphism, e.g., czm_getMaterial.
  135. var combinedSources = "";
  136. var sources = shaderSource.sources;
  137. if (defined(sources)) {
  138. for (i = 0, length = sources.length; i < length; ++i) {
  139. // #line needs to be on its own line.
  140. combinedSources += "\n#line 0\n" + sources[i];
  141. }
  142. }
  143. combinedSources = removeComments(combinedSources);
  144. // Extract existing shader version from sources
  145. var version;
  146. combinedSources = combinedSources.replace(/#version\s+(.*?)\n/gm, function (
  147. match,
  148. group1
  149. ) {
  150. //>>includeStart('debug', pragmas.debug);
  151. if (defined(version) && version !== group1) {
  152. throw new DeveloperError(
  153. "inconsistent versions found: " + version + " and " + group1
  154. );
  155. }
  156. //>>includeEnd('debug');
  157. // Extract #version to put at the top
  158. version = group1;
  159. // Replace original #version directive with a new line so the line numbers
  160. // are not off by one. There can be only one #version directive
  161. // and it must appear at the top of the source, only preceded by
  162. // whitespace and comments.
  163. return "\n";
  164. });
  165. // Extract shader extensions from sources
  166. var extensions = [];
  167. combinedSources = combinedSources.replace(/#extension.*\n/gm, function (
  168. match
  169. ) {
  170. // Extract extension to put at the top
  171. extensions.push(match);
  172. // Replace original #extension directive with a new line so the line numbers
  173. // are not off by one.
  174. return "\n";
  175. });
  176. // Remove precision qualifier
  177. combinedSources = combinedSources.replace(
  178. /precision\s(lowp|mediump|highp)\s(float|int);/,
  179. ""
  180. );
  181. // Replace main() for picked if desired.
  182. var pickColorQualifier = shaderSource.pickColorQualifier;
  183. if (defined(pickColorQualifier)) {
  184. combinedSources = ShaderSource.createPickFragmentShaderSource(
  185. combinedSources,
  186. pickColorQualifier
  187. );
  188. }
  189. // combine into single string
  190. var result = "";
  191. // #version must be first
  192. // defaults to #version 100 if not specified
  193. if (defined(version)) {
  194. result = "#version " + version + "\n";
  195. }
  196. var extensionsLength = extensions.length;
  197. for (i = 0; i < extensionsLength; i++) {
  198. result += extensions[i];
  199. }
  200. if (isFragmentShader) {
  201. result +=
  202. "\
  203. #ifdef GL_FRAGMENT_PRECISION_HIGH\n\
  204. precision highp float;\n\
  205. #else\n\
  206. precision mediump float;\n\
  207. #endif\n\n";
  208. }
  209. // Prepend #defines for uber-shaders
  210. var defines = shaderSource.defines;
  211. if (defined(defines)) {
  212. for (i = 0, length = defines.length; i < length; ++i) {
  213. var define = defines[i];
  214. if (define.length !== 0) {
  215. result += "#define " + define + "\n";
  216. }
  217. }
  218. }
  219. // GLSLModernizer inserts its own layout qualifiers
  220. // at this position in the source
  221. if (context.webgl2) {
  222. result += "#define OUTPUT_DECLARATION\n\n";
  223. }
  224. // Define a constant for the OES_texture_float_linear extension since WebGL does not.
  225. if (context.textureFloatLinear) {
  226. result += "#define OES_texture_float_linear\n\n";
  227. }
  228. // append built-ins
  229. if (shaderSource.includeBuiltIns) {
  230. result += getBuiltinsAndAutomaticUniforms(combinedSources);
  231. }
  232. // reset line number
  233. result += "\n#line 0\n";
  234. // append actual source
  235. result += combinedSources;
  236. // modernize the source
  237. if (context.webgl2) {
  238. result = modernizeShader(result, isFragmentShader, true);
  239. }
  240. return result;
  241. }
  242. /**
  243. * An object containing various inputs that will be combined to form a final GLSL shader string.
  244. *
  245. * @param {Object} [options] Object with the following properties:
  246. * @param {String[]} [options.sources] An array of strings to combine containing GLSL code for the shader.
  247. * @param {String[]} [options.defines] An array of strings containing GLSL identifiers to <code>#define</code>.
  248. * @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.
  249. * @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.
  250. *
  251. * @exception {DeveloperError} options.pickColorQualifier must be 'uniform' or 'varying'.
  252. *
  253. * @example
  254. * // 1. Prepend #defines to a shader
  255. * var source = new Cesium.ShaderSource({
  256. * defines : ['WHITE'],
  257. * sources : ['void main() { \n#ifdef WHITE\n gl_FragColor = vec4(1.0); \n#else\n gl_FragColor = vec4(0.0); \n#endif\n }']
  258. * });
  259. *
  260. * // 2. Modify a fragment shader for picking
  261. * var source = new Cesium.ShaderSource({
  262. * sources : ['void main() { gl_FragColor = vec4(1.0); }'],
  263. * pickColorQualifier : 'uniform'
  264. * });
  265. *
  266. * @private
  267. */
  268. function ShaderSource(options) {
  269. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  270. var pickColorQualifier = options.pickColorQualifier;
  271. //>>includeStart('debug', pragmas.debug);
  272. if (
  273. defined(pickColorQualifier) &&
  274. pickColorQualifier !== "uniform" &&
  275. pickColorQualifier !== "varying"
  276. ) {
  277. throw new DeveloperError(
  278. "options.pickColorQualifier must be 'uniform' or 'varying'."
  279. );
  280. }
  281. //>>includeEnd('debug');
  282. this.defines = defined(options.defines) ? options.defines.slice(0) : [];
  283. this.sources = defined(options.sources) ? options.sources.slice(0) : [];
  284. this.pickColorQualifier = pickColorQualifier;
  285. this.includeBuiltIns = defaultValue(options.includeBuiltIns, true);
  286. }
  287. ShaderSource.prototype.clone = function () {
  288. return new ShaderSource({
  289. sources: this.sources,
  290. defines: this.defines,
  291. pickColorQualifier: this.pickColorQualifier,
  292. includeBuiltIns: this.includeBuiltIns,
  293. });
  294. };
  295. ShaderSource.replaceMain = function (source, renamedMain) {
  296. renamedMain = "void " + renamedMain + "()";
  297. return source.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, renamedMain);
  298. };
  299. /**
  300. * Create a single string containing the full, combined vertex shader with all dependencies and defines.
  301. *
  302. * @param {Context} context The current rendering context
  303. *
  304. * @returns {String} The combined shader string.
  305. */
  306. ShaderSource.prototype.createCombinedVertexShader = function (context) {
  307. return combineShader(this, false, context);
  308. };
  309. /**
  310. * Create a single string containing the full, combined fragment shader with all dependencies and defines.
  311. *
  312. * @param {Context} context The current rendering context
  313. *
  314. * @returns {String} The combined shader string.
  315. */
  316. ShaderSource.prototype.createCombinedFragmentShader = function (context) {
  317. return combineShader(this, true, context);
  318. };
  319. /**
  320. * For ShaderProgram testing
  321. * @private
  322. */
  323. ShaderSource._czmBuiltinsAndUniforms = {};
  324. // combine automatic uniforms and Cesium built-ins
  325. for (var builtinName in CzmBuiltins) {
  326. if (CzmBuiltins.hasOwnProperty(builtinName)) {
  327. ShaderSource._czmBuiltinsAndUniforms[builtinName] =
  328. CzmBuiltins[builtinName];
  329. }
  330. }
  331. for (var uniformName in AutomaticUniforms) {
  332. if (AutomaticUniforms.hasOwnProperty(uniformName)) {
  333. var uniform = AutomaticUniforms[uniformName];
  334. if (typeof uniform.getDeclaration === "function") {
  335. ShaderSource._czmBuiltinsAndUniforms[
  336. uniformName
  337. ] = uniform.getDeclaration(uniformName);
  338. }
  339. }
  340. }
  341. ShaderSource.createPickVertexShaderSource = function (vertexShaderSource) {
  342. var renamedVS = ShaderSource.replaceMain(vertexShaderSource, "czm_old_main");
  343. var pickMain =
  344. "attribute vec4 pickColor; \n" +
  345. "varying vec4 czm_pickColor; \n" +
  346. "void main() \n" +
  347. "{ \n" +
  348. " czm_old_main(); \n" +
  349. " czm_pickColor = pickColor; \n" +
  350. "}";
  351. return renamedVS + "\n" + pickMain;
  352. };
  353. ShaderSource.createPickFragmentShaderSource = function (
  354. fragmentShaderSource,
  355. pickColorQualifier
  356. ) {
  357. var renamedFS = ShaderSource.replaceMain(
  358. fragmentShaderSource,
  359. "czm_old_main"
  360. );
  361. var pickMain =
  362. pickColorQualifier +
  363. " vec4 czm_pickColor; \n" +
  364. "void main() \n" +
  365. "{ \n" +
  366. " czm_old_main(); \n" +
  367. " if (gl_FragColor.a == 0.0) { \n" +
  368. " discard; \n" +
  369. " } \n" +
  370. " gl_FragColor = czm_pickColor; \n" +
  371. "}";
  372. return renamedFS + "\n" + pickMain;
  373. };
  374. ShaderSource.findVarying = function (shaderSource, names) {
  375. var sources = shaderSource.sources;
  376. var namesLength = names.length;
  377. for (var i = 0; i < namesLength; ++i) {
  378. var name = names[i];
  379. var sourcesLength = sources.length;
  380. for (var j = 0; j < sourcesLength; ++j) {
  381. if (sources[j].indexOf(name) !== -1) {
  382. return name;
  383. }
  384. }
  385. }
  386. return undefined;
  387. };
  388. var normalVaryingNames = ["v_normalEC", "v_normal"];
  389. ShaderSource.findNormalVarying = function (shaderSource) {
  390. return ShaderSource.findVarying(shaderSource, normalVaryingNames);
  391. };
  392. var positionVaryingNames = ["v_positionEC"];
  393. ShaderSource.findPositionVarying = function (shaderSource) {
  394. return ShaderSource.findVarying(shaderSource, positionVaryingNames);
  395. };
  396. export default ShaderSource;