ShaderProgram.js 20 KB

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