ShaderProgram.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  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. const attributeLocations = shader._attributeLocations;
  174. if (defined(attributeLocations)) {
  175. for (const attribute in attributeLocations) {
  176. if (attributeLocations.hasOwnProperty(attribute)) {
  177. gl.bindAttribLocation(
  178. program,
  179. attributeLocations[attribute],
  180. attribute
  181. );
  182. }
  183. }
  184. }
  185. gl.linkProgram(program);
  186. let log;
  187. // For performance: if linker succeeds, return without checking compile status
  188. if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
  189. if (shader._logShaderCompilation) {
  190. log = gl.getShaderInfoLog(vertexShader);
  191. if (defined(log) && log.length > 0) {
  192. console.log(`${consolePrefix}Vertex shader compile log: ${log}`);
  193. }
  194. log = gl.getShaderInfoLog(fragmentShader);
  195. if (defined(log) && log.length > 0) {
  196. console.log(`${consolePrefix}Fragment shader compile log: ${log}`);
  197. }
  198. log = gl.getProgramInfoLog(program);
  199. if (defined(log) && log.length > 0) {
  200. console.log(`${consolePrefix}Shader program link log: ${log}`);
  201. }
  202. }
  203. gl.deleteShader(vertexShader);
  204. gl.deleteShader(fragmentShader);
  205. return program;
  206. }
  207. // Program failed to link. Try to find and report the reason
  208. let errorMessage;
  209. const debugShaders = shader._debugShaders;
  210. if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
  211. log = gl.getShaderInfoLog(fragmentShader);
  212. console.error(`${consolePrefix}Fragment shader compile log: ${log}`);
  213. console.error(`${consolePrefix} Fragment shader source:\n${fsSource}`);
  214. errorMessage = `Fragment shader failed to compile. Compile log: ${log}`;
  215. } else if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
  216. log = gl.getShaderInfoLog(vertexShader);
  217. console.error(`${consolePrefix}Vertex shader compile log: ${log}`);
  218. console.error(`${consolePrefix} Vertex shader source:\n${vsSource}`);
  219. errorMessage = `Vertex shader failed to compile. Compile log: ${log}`;
  220. } else {
  221. log = gl.getProgramInfoLog(program);
  222. console.error(`${consolePrefix}Shader program link log: ${log}`);
  223. logTranslatedSource(vertexShader, "vertex");
  224. logTranslatedSource(fragmentShader, "fragment");
  225. errorMessage = `Program failed to link. Link log: ${log}`;
  226. }
  227. gl.deleteShader(vertexShader);
  228. gl.deleteShader(fragmentShader);
  229. gl.deleteProgram(program);
  230. throw new RuntimeError(errorMessage);
  231. function logTranslatedSource(compiledShader, name) {
  232. if (!defined(debugShaders)) {
  233. return;
  234. }
  235. const translation = debugShaders.getTranslatedShaderSource(compiledShader);
  236. if (translation === "") {
  237. console.error(`${consolePrefix}${name} shader translation failed.`);
  238. return;
  239. }
  240. console.error(
  241. `${consolePrefix}Translated ${name} shaderSource:\n${translation}`
  242. );
  243. }
  244. }
  245. function findVertexAttributes(gl, program, numberOfAttributes) {
  246. const attributes = {};
  247. for (let i = 0; i < numberOfAttributes; ++i) {
  248. const attr = gl.getActiveAttrib(program, i);
  249. const location = gl.getAttribLocation(program, attr.name);
  250. attributes[attr.name] = {
  251. name: attr.name,
  252. type: attr.type,
  253. index: location,
  254. };
  255. }
  256. return attributes;
  257. }
  258. function findUniforms(gl, program) {
  259. const uniformsByName = {};
  260. const uniforms = [];
  261. const samplerUniforms = [];
  262. const numberOfUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
  263. for (let i = 0; i < numberOfUniforms; ++i) {
  264. const activeUniform = gl.getActiveUniform(program, i);
  265. const suffix = "[0]";
  266. const uniformName =
  267. activeUniform.name.indexOf(
  268. suffix,
  269. activeUniform.name.length - suffix.length
  270. ) !== -1
  271. ? activeUniform.name.slice(0, activeUniform.name.length - 3)
  272. : activeUniform.name;
  273. // Ignore GLSL built-in uniforms returned in Firefox.
  274. if (uniformName.indexOf("gl_") !== 0) {
  275. if (activeUniform.name.indexOf("[") < 0) {
  276. // Single uniform
  277. const location = gl.getUniformLocation(program, uniformName);
  278. // IE 11.0.9 needs this check since getUniformLocation can return null
  279. // if the uniform is not active (e.g., it is optimized out). Looks like
  280. // getActiveUniform() above returns uniforms that are not actually active.
  281. if (location !== null) {
  282. const uniform = createUniform(
  283. gl,
  284. activeUniform,
  285. uniformName,
  286. location
  287. );
  288. uniformsByName[uniformName] = uniform;
  289. uniforms.push(uniform);
  290. if (uniform._setSampler) {
  291. samplerUniforms.push(uniform);
  292. }
  293. }
  294. } else {
  295. // Uniform array
  296. let uniformArray;
  297. let locations;
  298. let value;
  299. let loc;
  300. // On some platforms - Nexus 4 in Firefox for one - an array of sampler2D ends up being represented
  301. // as separate uniforms, one for each array element. Check for and handle that case.
  302. const indexOfBracket = uniformName.indexOf("[");
  303. if (indexOfBracket >= 0) {
  304. // We're assuming the array elements show up in numerical order - it seems to be true.
  305. uniformArray = uniformsByName[uniformName.slice(0, indexOfBracket)];
  306. // Nexus 4 with Android 4.3 needs this check, because it reports a uniform
  307. // with the strange name webgl_3467e0265d05c3c1[1] in our globe surface shader.
  308. if (!defined(uniformArray)) {
  309. continue;
  310. }
  311. locations = uniformArray._locations;
  312. // On the Nexus 4 in Chrome, we get one uniform per sampler, just like in Firefox,
  313. // but the size is not 1 like it is in Firefox. So if we push locations here,
  314. // we'll end up adding too many locations.
  315. if (locations.length <= 1) {
  316. value = uniformArray.value;
  317. loc = gl.getUniformLocation(program, uniformName);
  318. // Workaround for IE 11.0.9. See above.
  319. if (loc !== null) {
  320. locations.push(loc);
  321. value.push(gl.getUniform(program, loc));
  322. }
  323. }
  324. } else {
  325. locations = [];
  326. for (let j = 0; j < activeUniform.size; ++j) {
  327. loc = gl.getUniformLocation(program, `${uniformName}[${j}]`);
  328. // Workaround for IE 11.0.9. See above.
  329. if (loc !== null) {
  330. locations.push(loc);
  331. }
  332. }
  333. uniformArray = createUniformArray(
  334. gl,
  335. activeUniform,
  336. uniformName,
  337. locations
  338. );
  339. uniformsByName[uniformName] = uniformArray;
  340. uniforms.push(uniformArray);
  341. if (uniformArray._setSampler) {
  342. samplerUniforms.push(uniformArray);
  343. }
  344. }
  345. }
  346. }
  347. }
  348. return {
  349. uniformsByName: uniformsByName,
  350. uniforms: uniforms,
  351. samplerUniforms: samplerUniforms,
  352. };
  353. }
  354. function partitionUniforms(shader, uniforms) {
  355. const automaticUniforms = [];
  356. const manualUniforms = [];
  357. for (const uniform in uniforms) {
  358. if (uniforms.hasOwnProperty(uniform)) {
  359. const uniformObject = uniforms[uniform];
  360. let uniformName = uniform;
  361. // if it's a duplicate uniform, use its original name so it is updated correctly
  362. const duplicateUniform = shader._duplicateUniformNames[uniformName];
  363. if (defined(duplicateUniform)) {
  364. uniformObject.name = duplicateUniform;
  365. uniformName = duplicateUniform;
  366. }
  367. const automaticUniform = AutomaticUniforms[uniformName];
  368. if (defined(automaticUniform)) {
  369. automaticUniforms.push({
  370. uniform: uniformObject,
  371. automaticUniform: automaticUniform,
  372. });
  373. } else {
  374. manualUniforms.push(uniformObject);
  375. }
  376. }
  377. }
  378. return {
  379. automaticUniforms: automaticUniforms,
  380. manualUniforms: manualUniforms,
  381. };
  382. }
  383. function setSamplerUniforms(gl, program, samplerUniforms) {
  384. gl.useProgram(program);
  385. let textureUnitIndex = 0;
  386. const length = samplerUniforms.length;
  387. for (let i = 0; i < length; ++i) {
  388. textureUnitIndex = samplerUniforms[i]._setSampler(textureUnitIndex);
  389. }
  390. gl.useProgram(null);
  391. return textureUnitIndex;
  392. }
  393. function initialize(shader) {
  394. if (defined(shader._program)) {
  395. return;
  396. }
  397. reinitialize(shader);
  398. }
  399. function reinitialize(shader) {
  400. const oldProgram = shader._program;
  401. const gl = shader._gl;
  402. const program = createAndLinkProgram(gl, shader, shader._debugShaders);
  403. const numberOfVertexAttributes = gl.getProgramParameter(
  404. program,
  405. gl.ACTIVE_ATTRIBUTES
  406. );
  407. const uniforms = findUniforms(gl, program);
  408. const partitionedUniforms = partitionUniforms(
  409. shader,
  410. uniforms.uniformsByName
  411. );
  412. shader._program = program;
  413. shader._numberOfVertexAttributes = numberOfVertexAttributes;
  414. shader._vertexAttributes = findVertexAttributes(
  415. gl,
  416. program,
  417. numberOfVertexAttributes
  418. );
  419. shader._uniformsByName = uniforms.uniformsByName;
  420. shader._uniforms = uniforms.uniforms;
  421. shader._automaticUniforms = partitionedUniforms.automaticUniforms;
  422. shader._manualUniforms = partitionedUniforms.manualUniforms;
  423. shader.maximumTextureUnitIndex = setSamplerUniforms(
  424. gl,
  425. program,
  426. uniforms.samplerUniforms
  427. );
  428. if (oldProgram) {
  429. shader._gl.deleteProgram(oldProgram);
  430. }
  431. // If SpectorJS is active, add the hook to make the shader editor work.
  432. // https://github.com/BabylonJS/Spector.js/blob/master/documentation/extension.md#shader-editor
  433. if (typeof spector !== "undefined") {
  434. shader._program.__SPECTOR_rebuildProgram = function (
  435. vertexSourceCode, // The new vertex shader source
  436. fragmentSourceCode, // The new fragment shader source
  437. onCompiled, // Callback triggered by your engine when the compilation is successful. It needs to send back the new linked program.
  438. 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.
  439. ) {
  440. const originalVS = shader._vertexShaderText;
  441. const originalFS = shader._fragmentShaderText;
  442. // SpectorJS likes to replace `!=` with `! =` for unknown reasons,
  443. // and that causes glsl compile failures. So fix that up.
  444. const regex = / ! = /g;
  445. shader._vertexShaderText = vertexSourceCode.replace(regex, " != ");
  446. shader._fragmentShaderText = fragmentSourceCode.replace(regex, " != ");
  447. try {
  448. reinitialize(shader);
  449. onCompiled(shader._program);
  450. } catch (e) {
  451. shader._vertexShaderText = originalVS;
  452. shader._fragmentShaderText = originalFS;
  453. // Only pass on the WebGL error:
  454. const errorMatcher = /(?:Compile|Link) error: ([^]*)/;
  455. const match = errorMatcher.exec(e.message);
  456. if (match) {
  457. onError(match[1]);
  458. } else {
  459. onError(e.message);
  460. }
  461. }
  462. };
  463. }
  464. }
  465. ShaderProgram.prototype._bind = function () {
  466. initialize(this);
  467. this._gl.useProgram(this._program);
  468. };
  469. ShaderProgram.prototype._setUniforms = function (
  470. uniformMap,
  471. uniformState,
  472. validate
  473. ) {
  474. let len;
  475. let i;
  476. if (defined(uniformMap)) {
  477. const manualUniforms = this._manualUniforms;
  478. len = manualUniforms.length;
  479. for (i = 0; i < len; ++i) {
  480. const mu = manualUniforms[i];
  481. mu.value = uniformMap[mu.name]();
  482. }
  483. }
  484. const automaticUniforms = this._automaticUniforms;
  485. len = automaticUniforms.length;
  486. for (i = 0; i < len; ++i) {
  487. const au = automaticUniforms[i];
  488. au.uniform.value = au.automaticUniform.getValue(uniformState);
  489. }
  490. ///////////////////////////////////////////////////////////////////
  491. // It appears that assigning the uniform values above and then setting them here
  492. // (which makes the GL calls) is faster than removing this loop and making
  493. // the GL calls above. I suspect this is because each GL call pollutes the
  494. // L2 cache making our JavaScript and the browser/driver ping-pong cache lines.
  495. const uniforms = this._uniforms;
  496. len = uniforms.length;
  497. for (i = 0; i < len; ++i) {
  498. uniforms[i].set();
  499. }
  500. if (validate) {
  501. const gl = this._gl;
  502. const program = this._program;
  503. gl.validateProgram(program);
  504. //>>includeStart('debug', pragmas.debug);
  505. if (!gl.getProgramParameter(program, gl.VALIDATE_STATUS)) {
  506. throw new DeveloperError(
  507. `Program validation failed. Program info log: ${gl.getProgramInfoLog(
  508. program
  509. )}`
  510. );
  511. }
  512. //>>includeEnd('debug');
  513. }
  514. };
  515. ShaderProgram.prototype.isDestroyed = function () {
  516. return false;
  517. };
  518. ShaderProgram.prototype.destroy = function () {
  519. this._cachedShader.cache.releaseShaderProgram(this);
  520. return undefined;
  521. };
  522. ShaderProgram.prototype.finalDestroy = function () {
  523. this._gl.deleteProgram(this._program);
  524. return destroyObject(this);
  525. };
  526. export default ShaderProgram;