ShaderProgram.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. import Check from "../Core/Check.js";
  2. import defaultValue from "../Core/defaultValue.js";
  3. import defined from "../Core/defined.js";
  4. import destroyObject from "../Core/destroyObject.js";
  5. import DeveloperError from "../Core/DeveloperError.js";
  6. import RuntimeError from "../Core/RuntimeError.js";
  7. import AutomaticUniforms from "./AutomaticUniforms.js";
  8. import ContextLimits from "./ContextLimits.js";
  9. import createUniform from "./createUniform.js";
  10. import createUniformArray from "./createUniformArray.js";
  11. let nextShaderProgramId = 0;
  12. /**
  13. * @private
  14. */
  15. function ShaderProgram(options) {
  16. let vertexShaderText = options.vertexShaderText;
  17. let fragmentShaderText = options.fragmentShaderText;
  18. if (typeof spector !== "undefined") {
  19. // The #line statements common in Cesium shaders interfere with the ability of the
  20. // SpectorJS to show errors on the correct line. So remove them when SpectorJS
  21. // is active.
  22. vertexShaderText = vertexShaderText.replace(/^#line/gm, "//#line");
  23. fragmentShaderText = fragmentShaderText.replace(/^#line/gm, "//#line");
  24. }
  25. const modifiedFS = handleUniformPrecisionMismatches(
  26. vertexShaderText,
  27. fragmentShaderText
  28. );
  29. this._gl = options.gl;
  30. this._logShaderCompilation = options.logShaderCompilation;
  31. this._debugShaders = options.debugShaders;
  32. this._attributeLocations = options.attributeLocations;
  33. this._program = undefined;
  34. this._numberOfVertexAttributes = undefined;
  35. this._vertexAttributes = undefined;
  36. this._uniformsByName = undefined;
  37. this._uniforms = undefined;
  38. this._automaticUniforms = undefined;
  39. this._manualUniforms = undefined;
  40. this._duplicateUniformNames = modifiedFS.duplicateUniformNames;
  41. this._cachedShader = undefined; // Used by ShaderCache
  42. /**
  43. * @private
  44. */
  45. this.maximumTextureUnitIndex = undefined;
  46. this._vertexShaderSource = options.vertexShaderSource;
  47. this._vertexShaderText = options.vertexShaderText;
  48. this._fragmentShaderSource = options.fragmentShaderSource;
  49. this._fragmentShaderText = modifiedFS.fragmentShaderText;
  50. /**
  51. * @private
  52. */
  53. this.id = nextShaderProgramId++;
  54. }
  55. ShaderProgram.fromCache = function (options) {
  56. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  57. //>>includeStart('debug', pragmas.debug);
  58. Check.defined("options.context", options.context);
  59. //>>includeEnd('debug');
  60. return options.context.shaderCache.getShaderProgram(options);
  61. };
  62. ShaderProgram.replaceCache = function (options) {
  63. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  64. //>>includeStart('debug', pragmas.debug);
  65. Check.defined("options.context", options.context);
  66. //>>includeEnd('debug');
  67. return options.context.shaderCache.replaceShaderProgram(options);
  68. };
  69. Object.defineProperties(ShaderProgram.prototype, {
  70. /**
  71. * GLSL source for the shader program's vertex shader.
  72. * @memberof ShaderProgram.prototype
  73. *
  74. * @type {ShaderSource}
  75. * @readonly
  76. */
  77. vertexShaderSource: {
  78. get: function () {
  79. return this._vertexShaderSource;
  80. },
  81. },
  82. /**
  83. * GLSL source for the shader program's fragment shader.
  84. * @memberof ShaderProgram.prototype
  85. *
  86. * @type {ShaderSource}
  87. * @readonly
  88. */
  89. fragmentShaderSource: {
  90. get: function () {
  91. return this._fragmentShaderSource;
  92. },
  93. },
  94. vertexAttributes: {
  95. get: function () {
  96. initialize(this);
  97. return this._vertexAttributes;
  98. },
  99. },
  100. numberOfVertexAttributes: {
  101. get: function () {
  102. initialize(this);
  103. return this._numberOfVertexAttributes;
  104. },
  105. },
  106. allUniforms: {
  107. get: function () {
  108. initialize(this);
  109. return this._uniformsByName;
  110. },
  111. },
  112. });
  113. function extractUniforms(shaderText) {
  114. const uniformNames = [];
  115. const uniformLines = shaderText.match(/uniform.*?(?![^{]*})(?=[=\[;])/g);
  116. if (defined(uniformLines)) {
  117. const len = uniformLines.length;
  118. for (let i = 0; i < len; i++) {
  119. const line = uniformLines[i].trim();
  120. const name = line.slice(line.lastIndexOf(" ") + 1);
  121. uniformNames.push(name);
  122. }
  123. }
  124. return uniformNames;
  125. }
  126. function handleUniformPrecisionMismatches(
  127. vertexShaderText,
  128. fragmentShaderText
  129. ) {
  130. // If a uniform exists in both the vertex and fragment shader but with different precision qualifiers,
  131. // give the fragment shader uniform a different name. This fixes shader compilation errors on devices
  132. // that only support mediump in the fragment shader.
  133. const duplicateUniformNames = {};
  134. if (!ContextLimits.highpFloatSupported || !ContextLimits.highpIntSupported) {
  135. let i, j;
  136. let uniformName;
  137. let duplicateName;
  138. const vertexShaderUniforms = extractUniforms(vertexShaderText);
  139. const fragmentShaderUniforms = extractUniforms(fragmentShaderText);
  140. const vertexUniformsCount = vertexShaderUniforms.length;
  141. const fragmentUniformsCount = fragmentShaderUniforms.length;
  142. for (i = 0; i < vertexUniformsCount; i++) {
  143. for (j = 0; j < fragmentUniformsCount; j++) {
  144. if (vertexShaderUniforms[i] === fragmentShaderUniforms[j]) {
  145. uniformName = vertexShaderUniforms[i];
  146. duplicateName = `czm_mediump_${uniformName}`;
  147. // Update fragmentShaderText with renamed uniforms
  148. const re = new RegExp(`${uniformName}\\b`, "g");
  149. fragmentShaderText = fragmentShaderText.replace(re, duplicateName);
  150. duplicateUniformNames[duplicateName] = uniformName;
  151. }
  152. }
  153. }
  154. }
  155. return {
  156. fragmentShaderText: fragmentShaderText,
  157. duplicateUniformNames: duplicateUniformNames,
  158. };
  159. }
  160. const consolePrefix = "[Cesium WebGL] ";
  161. function createAndLinkProgram(gl, shader) {
  162. const vsSource = shader._vertexShaderText;
  163. const fsSource = shader._fragmentShaderText;
  164. const vertexShader = gl.createShader(gl.VERTEX_SHADER);
  165. gl.shaderSource(vertexShader, vsSource);
  166. gl.compileShader(vertexShader);
  167. const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
  168. gl.shaderSource(fragmentShader, fsSource);
  169. gl.compileShader(fragmentShader);
  170. const program = gl.createProgram();
  171. gl.attachShader(program, vertexShader);
  172. gl.attachShader(program, fragmentShader);
  173. gl.deleteShader(vertexShader);
  174. gl.deleteShader(fragmentShader);
  175. const attributeLocations = shader._attributeLocations;
  176. if (defined(attributeLocations)) {
  177. for (const attribute in attributeLocations) {
  178. if (attributeLocations.hasOwnProperty(attribute)) {
  179. gl.bindAttribLocation(
  180. program,
  181. attributeLocations[attribute],
  182. attribute
  183. );
  184. }
  185. }
  186. }
  187. gl.linkProgram(program);
  188. let log;
  189. if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  190. const debugShaders = shader._debugShaders;
  191. // For performance, only check compile errors if there is a linker error.
  192. if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
  193. log = gl.getShaderInfoLog(fragmentShader);
  194. console.error(`${consolePrefix}Fragment shader compile log: ${log}`);
  195. if (defined(debugShaders)) {
  196. const fragmentSourceTranslation = debugShaders.getTranslatedShaderSource(
  197. fragmentShader
  198. );
  199. if (fragmentSourceTranslation !== "") {
  200. console.error(
  201. `${consolePrefix}Translated fragment shader source:\n${fragmentSourceTranslation}`
  202. );
  203. } else {
  204. console.error(`${consolePrefix}Fragment shader translation failed.`);
  205. }
  206. }
  207. gl.deleteProgram(program);
  208. throw new RuntimeError(
  209. `Fragment shader failed to compile. Compile log: ${log}`
  210. );
  211. }
  212. if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
  213. log = gl.getShaderInfoLog(vertexShader);
  214. console.error(`${consolePrefix}Vertex shader compile log: ${log}`);
  215. if (defined(debugShaders)) {
  216. const vertexSourceTranslation = debugShaders.getTranslatedShaderSource(
  217. vertexShader
  218. );
  219. if (vertexSourceTranslation !== "") {
  220. console.error(
  221. `${consolePrefix}Translated vertex shader source:\n${vertexSourceTranslation}`
  222. );
  223. } else {
  224. console.error(`${consolePrefix}Vertex shader translation failed.`);
  225. }
  226. }
  227. gl.deleteProgram(program);
  228. throw new RuntimeError(
  229. `Vertex shader failed to compile. Compile log: ${log}`
  230. );
  231. }
  232. log = gl.getProgramInfoLog(program);
  233. console.error(`${consolePrefix}Shader program link log: ${log}`);
  234. if (defined(debugShaders)) {
  235. console.error(
  236. `${consolePrefix}Translated vertex shader source:\n${debugShaders.getTranslatedShaderSource(
  237. vertexShader
  238. )}`
  239. );
  240. console.error(
  241. `${consolePrefix}Translated fragment shader source:\n${debugShaders.getTranslatedShaderSource(
  242. fragmentShader
  243. )}`
  244. );
  245. }
  246. gl.deleteProgram(program);
  247. throw new RuntimeError(`Program failed to link. Link log: ${log}`);
  248. }
  249. const logShaderCompilation = shader._logShaderCompilation;
  250. if (logShaderCompilation) {
  251. log = gl.getShaderInfoLog(vertexShader);
  252. if (defined(log) && log.length > 0) {
  253. console.log(`${consolePrefix}Vertex shader compile log: ${log}`);
  254. }
  255. }
  256. if (logShaderCompilation) {
  257. log = gl.getShaderInfoLog(fragmentShader);
  258. if (defined(log) && log.length > 0) {
  259. console.log(`${consolePrefix}Fragment shader compile log: ${log}`);
  260. }
  261. }
  262. if (logShaderCompilation) {
  263. log = gl.getProgramInfoLog(program);
  264. if (defined(log) && log.length > 0) {
  265. console.log(`${consolePrefix}Shader program link log: ${log}`);
  266. }
  267. }
  268. return program;
  269. }
  270. function findVertexAttributes(gl, program, numberOfAttributes) {
  271. const attributes = {};
  272. for (let i = 0; i < numberOfAttributes; ++i) {
  273. const attr = gl.getActiveAttrib(program, i);
  274. const location = gl.getAttribLocation(program, attr.name);
  275. attributes[attr.name] = {
  276. name: attr.name,
  277. type: attr.type,
  278. index: location,
  279. };
  280. }
  281. return attributes;
  282. }
  283. function findUniforms(gl, program) {
  284. const uniformsByName = {};
  285. const uniforms = [];
  286. const samplerUniforms = [];
  287. const numberOfUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
  288. for (let i = 0; i < numberOfUniforms; ++i) {
  289. const activeUniform = gl.getActiveUniform(program, i);
  290. const suffix = "[0]";
  291. const uniformName =
  292. activeUniform.name.indexOf(
  293. suffix,
  294. activeUniform.name.length - suffix.length
  295. ) !== -1
  296. ? activeUniform.name.slice(0, activeUniform.name.length - 3)
  297. : activeUniform.name;
  298. // Ignore GLSL built-in uniforms returned in Firefox.
  299. if (uniformName.indexOf("gl_") !== 0) {
  300. if (activeUniform.name.indexOf("[") < 0) {
  301. // Single uniform
  302. const location = gl.getUniformLocation(program, uniformName);
  303. // IE 11.0.9 needs this check since getUniformLocation can return null
  304. // if the uniform is not active (e.g., it is optimized out). Looks like
  305. // getActiveUniform() above returns uniforms that are not actually active.
  306. if (location !== null) {
  307. const uniform = createUniform(
  308. gl,
  309. activeUniform,
  310. uniformName,
  311. location
  312. );
  313. uniformsByName[uniformName] = uniform;
  314. uniforms.push(uniform);
  315. if (uniform._setSampler) {
  316. samplerUniforms.push(uniform);
  317. }
  318. }
  319. } else {
  320. // Uniform array
  321. let uniformArray;
  322. let locations;
  323. let value;
  324. let loc;
  325. // On some platforms - Nexus 4 in Firefox for one - an array of sampler2D ends up being represented
  326. // as separate uniforms, one for each array element. Check for and handle that case.
  327. const indexOfBracket = uniformName.indexOf("[");
  328. if (indexOfBracket >= 0) {
  329. // We're assuming the array elements show up in numerical order - it seems to be true.
  330. uniformArray = uniformsByName[uniformName.slice(0, indexOfBracket)];
  331. // Nexus 4 with Android 4.3 needs this check, because it reports a uniform
  332. // with the strange name webgl_3467e0265d05c3c1[1] in our globe surface shader.
  333. if (!defined(uniformArray)) {
  334. continue;
  335. }
  336. locations = uniformArray._locations;
  337. // On the Nexus 4 in Chrome, we get one uniform per sampler, just like in Firefox,
  338. // but the size is not 1 like it is in Firefox. So if we push locations here,
  339. // we'll end up adding too many locations.
  340. if (locations.length <= 1) {
  341. value = uniformArray.value;
  342. loc = gl.getUniformLocation(program, uniformName);
  343. // Workaround for IE 11.0.9. See above.
  344. if (loc !== null) {
  345. locations.push(loc);
  346. value.push(gl.getUniform(program, loc));
  347. }
  348. }
  349. } else {
  350. locations = [];
  351. for (let j = 0; j < activeUniform.size; ++j) {
  352. loc = gl.getUniformLocation(program, `${uniformName}[${j}]`);
  353. // Workaround for IE 11.0.9. See above.
  354. if (loc !== null) {
  355. locations.push(loc);
  356. }
  357. }
  358. uniformArray = createUniformArray(
  359. gl,
  360. activeUniform,
  361. uniformName,
  362. locations
  363. );
  364. uniformsByName[uniformName] = uniformArray;
  365. uniforms.push(uniformArray);
  366. if (uniformArray._setSampler) {
  367. samplerUniforms.push(uniformArray);
  368. }
  369. }
  370. }
  371. }
  372. }
  373. return {
  374. uniformsByName: uniformsByName,
  375. uniforms: uniforms,
  376. samplerUniforms: samplerUniforms,
  377. };
  378. }
  379. function partitionUniforms(shader, uniforms) {
  380. const automaticUniforms = [];
  381. const manualUniforms = [];
  382. for (const uniform in uniforms) {
  383. if (uniforms.hasOwnProperty(uniform)) {
  384. const uniformObject = uniforms[uniform];
  385. let uniformName = uniform;
  386. // if it's a duplicate uniform, use its original name so it is updated correctly
  387. const duplicateUniform = shader._duplicateUniformNames[uniformName];
  388. if (defined(duplicateUniform)) {
  389. uniformObject.name = duplicateUniform;
  390. uniformName = duplicateUniform;
  391. }
  392. const automaticUniform = AutomaticUniforms[uniformName];
  393. if (defined(automaticUniform)) {
  394. automaticUniforms.push({
  395. uniform: uniformObject,
  396. automaticUniform: automaticUniform,
  397. });
  398. } else {
  399. manualUniforms.push(uniformObject);
  400. }
  401. }
  402. }
  403. return {
  404. automaticUniforms: automaticUniforms,
  405. manualUniforms: manualUniforms,
  406. };
  407. }
  408. function setSamplerUniforms(gl, program, samplerUniforms) {
  409. gl.useProgram(program);
  410. let textureUnitIndex = 0;
  411. const length = samplerUniforms.length;
  412. for (let i = 0; i < length; ++i) {
  413. textureUnitIndex = samplerUniforms[i]._setSampler(textureUnitIndex);
  414. }
  415. gl.useProgram(null);
  416. return textureUnitIndex;
  417. }
  418. function initialize(shader) {
  419. if (defined(shader._program)) {
  420. return;
  421. }
  422. reinitialize(shader);
  423. }
  424. function reinitialize(shader) {
  425. const oldProgram = shader._program;
  426. const gl = shader._gl;
  427. const program = createAndLinkProgram(gl, shader, shader._debugShaders);
  428. const numberOfVertexAttributes = gl.getProgramParameter(
  429. program,
  430. gl.ACTIVE_ATTRIBUTES
  431. );
  432. const uniforms = findUniforms(gl, program);
  433. const partitionedUniforms = partitionUniforms(
  434. shader,
  435. uniforms.uniformsByName
  436. );
  437. shader._program = program;
  438. shader._numberOfVertexAttributes = numberOfVertexAttributes;
  439. shader._vertexAttributes = findVertexAttributes(
  440. gl,
  441. program,
  442. numberOfVertexAttributes
  443. );
  444. shader._uniformsByName = uniforms.uniformsByName;
  445. shader._uniforms = uniforms.uniforms;
  446. shader._automaticUniforms = partitionedUniforms.automaticUniforms;
  447. shader._manualUniforms = partitionedUniforms.manualUniforms;
  448. shader.maximumTextureUnitIndex = setSamplerUniforms(
  449. gl,
  450. program,
  451. uniforms.samplerUniforms
  452. );
  453. if (oldProgram) {
  454. shader._gl.deleteProgram(oldProgram);
  455. }
  456. // If SpectorJS is active, add the hook to make the shader editor work.
  457. // https://github.com/BabylonJS/Spector.js/blob/master/documentation/extension.md#shader-editor
  458. if (typeof spector !== "undefined") {
  459. shader._program.__SPECTOR_rebuildProgram = function (
  460. vertexSourceCode, // The new vertex shader source
  461. fragmentSourceCode, // The new fragment shader source
  462. onCompiled, // Callback triggered by your engine when the compilation is successful. It needs to send back the new linked program.
  463. onError // Callback triggered by your engine in case of error. It needs to send the WebGL error to allow the editor to display the error in the gutter.
  464. ) {
  465. const originalVS = shader._vertexShaderText;
  466. const originalFS = shader._fragmentShaderText;
  467. // SpectorJS likes to replace `!=` with `! =` for unknown reasons,
  468. // and that causes glsl compile failures. So fix that up.
  469. const regex = / ! = /g;
  470. shader._vertexShaderText = vertexSourceCode.replace(regex, " != ");
  471. shader._fragmentShaderText = fragmentSourceCode.replace(regex, " != ");
  472. try {
  473. reinitialize(shader);
  474. onCompiled(shader._program);
  475. } catch (e) {
  476. shader._vertexShaderText = originalVS;
  477. shader._fragmentShaderText = originalFS;
  478. // Only pass on the WebGL error:
  479. const errorMatcher = /(?:Compile|Link) error: ([^]*)/;
  480. const match = errorMatcher.exec(e.message);
  481. if (match) {
  482. onError(match[1]);
  483. } else {
  484. onError(e.message);
  485. }
  486. }
  487. };
  488. }
  489. }
  490. ShaderProgram.prototype._bind = function () {
  491. initialize(this);
  492. this._gl.useProgram(this._program);
  493. };
  494. ShaderProgram.prototype._setUniforms = function (
  495. uniformMap,
  496. uniformState,
  497. validate
  498. ) {
  499. let len;
  500. let i;
  501. if (defined(uniformMap)) {
  502. const manualUniforms = this._manualUniforms;
  503. len = manualUniforms.length;
  504. for (i = 0; i < len; ++i) {
  505. const mu = manualUniforms[i];
  506. mu.value = uniformMap[mu.name]();
  507. }
  508. }
  509. const automaticUniforms = this._automaticUniforms;
  510. len = automaticUniforms.length;
  511. for (i = 0; i < len; ++i) {
  512. const au = automaticUniforms[i];
  513. au.uniform.value = au.automaticUniform.getValue(uniformState);
  514. }
  515. ///////////////////////////////////////////////////////////////////
  516. // It appears that assigning the uniform values above and then setting them here
  517. // (which makes the GL calls) is faster than removing this loop and making
  518. // the GL calls above. I suspect this is because each GL call pollutes the
  519. // L2 cache making our JavaScript and the browser/driver ping-pong cache lines.
  520. const uniforms = this._uniforms;
  521. len = uniforms.length;
  522. for (i = 0; i < len; ++i) {
  523. uniforms[i].set();
  524. }
  525. if (validate) {
  526. const gl = this._gl;
  527. const program = this._program;
  528. gl.validateProgram(program);
  529. //>>includeStart('debug', pragmas.debug);
  530. if (!gl.getProgramParameter(program, gl.VALIDATE_STATUS)) {
  531. throw new DeveloperError(
  532. `Program validation failed. Program info log: ${gl.getProgramInfoLog(
  533. program
  534. )}`
  535. );
  536. }
  537. //>>includeEnd('debug');
  538. }
  539. };
  540. ShaderProgram.prototype.isDestroyed = function () {
  541. return false;
  542. };
  543. ShaderProgram.prototype.destroy = function () {
  544. this._cachedShader.cache.releaseShaderProgram(this);
  545. return undefined;
  546. };
  547. ShaderProgram.prototype.finalDestroy = function () {
  548. this._gl.deleteProgram(this._program);
  549. return destroyObject(this);
  550. };
  551. export default ShaderProgram;