智慧水务管理系统 - 精河县供水工程综合管理平台

ShaderSource.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. import Frozen from "../Core/Frozen.js";
  2. import defined from "../Core/defined.js";
  3. import DeveloperError from "../Core/DeveloperError.js";
  4. import CzmBuiltins from "../Shaders/Builtin/CzmBuiltins.js";
  5. import AutomaticUniforms from "./AutomaticUniforms.js";
  6. import demodernizeShader from "./demodernizeShader.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. const numberOfLines = match.match(/\n/gm).length;
  14. let replacement = "";
  15. for (let lineNumber = 0; lineNumber < numberOfLines; ++lineNumber) {
  16. replacement += "\n";
  17. }
  18. return replacement;
  19. });
  20. }
  21. function getDependencyNode(name, glslSource, nodes) {
  22. let dependencyNode;
  23. // check if already loaded
  24. for (let 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. let 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. const 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. const nodesWithoutIncomingEdges = [];
  77. const allNodes = [];
  78. while (dependencyNodes.length > 0) {
  79. const 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. const currentNode = nodesWithoutIncomingEdges.shift();
  87. dependencyNodes.push(currentNode);
  88. for (let i = 0; i < currentNode.dependsOn.length; ++i) {
  89. // remove the edge from the graph
  90. const referencedNode = currentNode.dependsOn[i];
  91. const 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. const badNodes = [];
  101. for (let 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. let message =
  109. "A circular dependency was found in the following built-in functions/structs/constants: \n";
  110. for (let 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. const dependencyNodes = [];
  120. const 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. let builtinsSource = "";
  126. for (let 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. // Combine shader sources, generally for pseudo-polymorphism, e.g., czm_getMaterial.
  133. let combinedSources = "";
  134. const sources = shaderSource.sources;
  135. if (defined(sources)) {
  136. for (let i = 0; i < sources.length; ++i) {
  137. // #line needs to be on its own line.
  138. combinedSources += `\n#line 0\n${sources[i]}`;
  139. }
  140. }
  141. combinedSources = removeComments(combinedSources);
  142. // Extract existing shader version from sources
  143. let version;
  144. combinedSources = combinedSources.replace(
  145. /#version\s+(.*?)\n/gm,
  146. function (match, group1) {
  147. //>>includeStart('debug', pragmas.debug);
  148. if (defined(version) && version !== group1) {
  149. throw new DeveloperError(
  150. `inconsistent versions found: ${version} and ${group1}`,
  151. );
  152. }
  153. //>>includeEnd('debug');
  154. // Extract #version to put at the top
  155. version = group1;
  156. // Replace original #version directive with a new line so the line numbers
  157. // are not off by one. There can be only one #version directive
  158. // and it must appear at the top of the source, only preceded by
  159. // whitespace and comments.
  160. return "\n";
  161. },
  162. );
  163. // Extract shader extensions from sources
  164. const extensions = [];
  165. combinedSources = combinedSources.replace(
  166. /#extension.*\n/gm,
  167. function (match) {
  168. // Extract extension to put at the top
  169. extensions.push(match);
  170. // Replace original #extension directive with a new line so the line numbers
  171. // are not off by one.
  172. return "\n";
  173. },
  174. );
  175. // Remove precision qualifier
  176. combinedSources = combinedSources.replace(
  177. /precision\s(lowp|mediump|highp)\s(float|int);/,
  178. "",
  179. );
  180. // Replace main() for picked if desired.
  181. const pickColorQualifier = shaderSource.pickColorQualifier;
  182. if (defined(pickColorQualifier)) {
  183. combinedSources = ShaderSource.createPickFragmentShaderSource(
  184. combinedSources,
  185. pickColorQualifier,
  186. );
  187. }
  188. // combine into single string
  189. let result = "";
  190. const extensionsLength = extensions.length;
  191. for (let i = 0; i < extensionsLength; i++) {
  192. result += extensions[i];
  193. }
  194. if (isFragmentShader) {
  195. // If high precision isn't supported, replace occurrences of highp with mediump.
  196. // The highp keyword is not always available on older mobile devices.
  197. // See https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#In_WebGL_1_highp_float_support_is_optional_in_fragment_shaders
  198. result += `
  199. #ifdef GL_FRAGMENT_PRECISION_HIGH
  200. precision highp float;
  201. precision highp int;
  202. #else
  203. precision mediump float;
  204. precision mediump int;
  205. #define highp mediump
  206. #endif
  207. `;
  208. }
  209. if (context.webgl2) {
  210. result += `precision highp sampler3D;\n\n`;
  211. }
  212. // Prepend #defines for uber-shaders
  213. const defines = shaderSource.defines;
  214. if (defined(defines)) {
  215. for (let i = 0, length = defines.length; i < length; ++i) {
  216. const define = defines[i];
  217. if (define.length !== 0) {
  218. result += `#define ${define}\n`;
  219. }
  220. }
  221. }
  222. // Define a constant for the OES_texture_float_linear extension since WebGL does not.
  223. if (context.textureFloatLinear) {
  224. result += "#define OES_texture_float_linear\n\n";
  225. }
  226. // Define a constant for the OES_texture_float extension since WebGL does not.
  227. if (context.floatingPointTexture) {
  228. result += "#define OES_texture_float\n\n";
  229. }
  230. // append built-ins
  231. let builtinSources = "";
  232. if (shaderSource.includeBuiltIns) {
  233. builtinSources = getBuiltinsAndAutomaticUniforms(combinedSources);
  234. }
  235. // reset line number
  236. result += "\n#line 0\n";
  237. // append actual source
  238. const combinedShader = builtinSources + combinedSources;
  239. if (
  240. context.webgl2 &&
  241. isFragmentShader &&
  242. !/layout\s*\(location\s*=\s*0\)\s*out\s+vec4\s+out_FragColor;/g.test(
  243. combinedShader,
  244. ) &&
  245. !/czm_out_FragColor/g.test(combinedShader) &&
  246. /out_FragColor/g.test(combinedShader)
  247. ) {
  248. result += "layout(location = 0) out vec4 out_FragColor;\n\n";
  249. }
  250. result += builtinSources;
  251. result += combinedSources;
  252. // modernize the source
  253. if (!context.webgl2) {
  254. result = demodernizeShader(result, isFragmentShader);
  255. } else {
  256. result = `#version 300 es\n${result}`;
  257. }
  258. return result;
  259. }
  260. /**
  261. * An object containing various inputs that will be combined to form a final GLSL shader string.
  262. *
  263. * @param {object} [options] Object with the following properties:
  264. * @param {string[]} [options.sources] An array of strings to combine containing GLSL code for the shader.
  265. * @param {string[]} [options.defines] An array of strings containing GLSL identifiers to <code>#define</code>.
  266. * @param {string} [options.pickColorQualifier] The GLSL qualifier, <code>uniform</code> or <code>in</code>, for the input <code>czm_pickColor</code>. When defined, a pick fragment shader is generated.
  267. * @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.
  268. *
  269. * @exception {DeveloperError} options.pickColorQualifier must be 'uniform' or 'in'.
  270. *
  271. * @example
  272. * // 1. Prepend #defines to a shader
  273. * const source = new Cesium.ShaderSource({
  274. * defines : ['WHITE'],
  275. * sources : ['void main() { \n#ifdef WHITE\n out_FragColor = vec4(1.0); \n#else\n out_FragColor = vec4(0.0); \n#endif\n }']
  276. * });
  277. *
  278. * // 2. Modify a fragment shader for picking
  279. * const source2 = new Cesium.ShaderSource({
  280. * sources : ['void main() { out_FragColor = vec4(1.0); }'],
  281. * pickColorQualifier : 'uniform'
  282. * });
  283. *
  284. * @private
  285. */
  286. function ShaderSource(options) {
  287. options = options ?? Frozen.EMPTY_OBJECT;
  288. const pickColorQualifier = options.pickColorQualifier;
  289. //>>includeStart('debug', pragmas.debug);
  290. if (
  291. defined(pickColorQualifier) &&
  292. pickColorQualifier !== "uniform" &&
  293. pickColorQualifier !== "in"
  294. ) {
  295. throw new DeveloperError(
  296. "options.pickColorQualifier must be 'uniform' or 'in'.",
  297. );
  298. }
  299. //>>includeEnd('debug');
  300. this.defines = defined(options.defines) ? options.defines.slice(0) : [];
  301. this.sources = defined(options.sources) ? options.sources.slice(0) : [];
  302. this.pickColorQualifier = pickColorQualifier;
  303. this.includeBuiltIns = options.includeBuiltIns ?? true;
  304. }
  305. ShaderSource.prototype.clone = function () {
  306. return new ShaderSource({
  307. sources: this.sources,
  308. defines: this.defines,
  309. pickColorQualifier: this.pickColorQualifier,
  310. includeBuiltIns: this.includeBuiltIns,
  311. });
  312. };
  313. ShaderSource.replaceMain = function (source, renamedMain) {
  314. renamedMain = `void ${renamedMain}()`;
  315. return source.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, renamedMain);
  316. };
  317. /**
  318. * Since {@link ShaderSource#createCombinedVertexShader} and
  319. * {@link ShaderSource#createCombinedFragmentShader} are both expensive to
  320. * compute, create a simpler string key for lookups in the {@link ShaderCache}.
  321. *
  322. * @returns {string} A key for identifying this shader
  323. *
  324. * @private
  325. */
  326. ShaderSource.prototype.getCacheKey = function () {
  327. // Sort defines to make the key comparison deterministic
  328. const sortedDefines = this.defines.slice().sort();
  329. const definesKey = sortedDefines.join(",");
  330. const pickKey = this.pickColorQualifier;
  331. const builtinsKey = this.includeBuiltIns;
  332. const sourcesKey = this.sources.join("\n");
  333. return `${definesKey}:${pickKey}:${builtinsKey}:${sourcesKey}`;
  334. };
  335. /**
  336. * Create a single string containing the full, combined vertex shader with all dependencies and defines.
  337. *
  338. * @param {Context} context The current rendering context
  339. *
  340. * @returns {string} The combined shader string.
  341. */
  342. ShaderSource.prototype.createCombinedVertexShader = function (context) {
  343. return combineShader(this, false, context);
  344. };
  345. /**
  346. * Create a single string containing the full, combined fragment shader with all dependencies and defines.
  347. *
  348. * @param {Context} context The current rendering context
  349. *
  350. * @returns {string} The combined shader string.
  351. */
  352. ShaderSource.prototype.createCombinedFragmentShader = function (context) {
  353. return combineShader(this, true, context);
  354. };
  355. /**
  356. * For ShaderProgram testing
  357. * @private
  358. */
  359. ShaderSource._czmBuiltinsAndUniforms = {};
  360. // combine automatic uniforms and Cesium built-ins
  361. for (const builtinName in CzmBuiltins) {
  362. if (CzmBuiltins.hasOwnProperty(builtinName)) {
  363. ShaderSource._czmBuiltinsAndUniforms[builtinName] =
  364. CzmBuiltins[builtinName];
  365. }
  366. }
  367. for (const uniformName in AutomaticUniforms) {
  368. if (AutomaticUniforms.hasOwnProperty(uniformName)) {
  369. const uniform = AutomaticUniforms[uniformName];
  370. if (typeof uniform.getDeclaration === "function") {
  371. ShaderSource._czmBuiltinsAndUniforms[uniformName] =
  372. uniform.getDeclaration(uniformName);
  373. }
  374. }
  375. }
  376. ShaderSource.createPickVertexShaderSource = function (vertexShaderSource) {
  377. const renamedVS = ShaderSource.replaceMain(
  378. vertexShaderSource,
  379. "czm_old_main",
  380. );
  381. const pickMain =
  382. "in vec4 pickColor; \n" +
  383. "out vec4 czm_pickColor; \n" +
  384. "void main() \n" +
  385. "{ \n" +
  386. " czm_old_main(); \n" +
  387. " czm_pickColor = pickColor; \n" +
  388. "}";
  389. return `${renamedVS}\n${pickMain}`;
  390. };
  391. ShaderSource.createPickFragmentShaderSource = function (
  392. fragmentShaderSource,
  393. pickColorQualifier,
  394. ) {
  395. const renamedFS = ShaderSource.replaceMain(
  396. fragmentShaderSource,
  397. "czm_old_main",
  398. );
  399. const pickMain =
  400. `${pickColorQualifier} vec4 czm_pickColor; \n` +
  401. `void main() \n` +
  402. `{ \n` +
  403. ` czm_old_main(); \n` +
  404. ` if (out_FragColor.a == 0.0) { \n` +
  405. ` discard; \n` +
  406. ` } \n` +
  407. ` out_FragColor = czm_pickColor; \n` +
  408. `}`;
  409. return `${renamedFS}\n${pickMain}`;
  410. };
  411. function containsDefine(shaderSource, define) {
  412. const defines = shaderSource.defines;
  413. const definesLength = defines.length;
  414. for (let i = 0; i < definesLength; ++i) {
  415. if (defines[i] === define) {
  416. return true;
  417. }
  418. }
  419. return false;
  420. }
  421. function containsString(shaderSource, string) {
  422. const sources = shaderSource.sources;
  423. const sourcesLength = sources.length;
  424. for (let i = 0; i < sourcesLength; ++i) {
  425. if (sources[i].indexOf(string) !== -1) {
  426. return true;
  427. }
  428. }
  429. return false;
  430. }
  431. function findFirstString(shaderSource, strings) {
  432. const stringsLength = strings.length;
  433. for (let i = 0; i < stringsLength; ++i) {
  434. const string = strings[i];
  435. if (containsString(shaderSource, string)) {
  436. return string;
  437. }
  438. }
  439. return undefined;
  440. }
  441. const normalVaryingNames = ["v_normalEC", "v_normal"];
  442. ShaderSource.findNormalVarying = function (shaderSource) {
  443. // Fix for Model: the shader text always has the word v_normalEC
  444. // wrapped in an #ifdef so instead of looking for v_normalEC look for the define
  445. if (containsString(shaderSource, "#ifdef HAS_NORMALS")) {
  446. if (containsDefine(shaderSource, "HAS_NORMALS")) {
  447. return "v_normalEC";
  448. }
  449. return undefined;
  450. }
  451. return findFirstString(shaderSource, normalVaryingNames);
  452. };
  453. const positionVaryingNames = ["v_positionEC"];
  454. ShaderSource.findPositionVarying = function (shaderSource) {
  455. return findFirstString(shaderSource, positionVaryingNames);
  456. };
  457. export default ShaderSource;