ShaderCache.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. import defined from "../Core/defined.js";
  2. import destroyObject from "../Core/destroyObject.js";
  3. import ShaderProgram from "./ShaderProgram.js";
  4. import ShaderSource from "./ShaderSource.js";
  5. /**
  6. * @private
  7. */
  8. function ShaderCache(context) {
  9. this._context = context;
  10. this._shaders = {};
  11. this._numberOfShaders = 0;
  12. this._shadersToRelease = {};
  13. }
  14. Object.defineProperties(ShaderCache.prototype, {
  15. numberOfShaders: {
  16. get: function () {
  17. return this._numberOfShaders;
  18. },
  19. },
  20. });
  21. /**
  22. * Returns a shader program from the cache, or creates and caches a new shader program,
  23. * given the GLSL vertex and fragment shader source and attribute locations.
  24. * <p>
  25. * The difference between this and {@link ShaderCache#getShaderProgram}, is this is used to
  26. * replace an existing reference to a shader program, which is passed as the first argument.
  27. * </p>
  28. *
  29. * @param {object} options Object with the following properties:
  30. * @param {ShaderProgram} [options.shaderProgram] The shader program that is being reassigned.
  31. * @param {string|ShaderSource} options.vertexShaderSource The GLSL source for the vertex shader.
  32. * @param {string|ShaderSource} options.fragmentShaderSource The GLSL source for the fragment shader.
  33. * @param {object} options.attributeLocations Indices for the attribute inputs to the vertex shader.
  34. * @returns {ShaderProgram} The cached or newly created shader program.
  35. *
  36. *
  37. * @example
  38. * this._shaderProgram = context.shaderCache.replaceShaderProgram({
  39. * shaderProgram : this._shaderProgram,
  40. * vertexShaderSource : vs,
  41. * fragmentShaderSource : fs,
  42. * attributeLocations : attributeLocations
  43. * });
  44. *
  45. * @see ShaderCache#getShaderProgram
  46. */
  47. ShaderCache.prototype.replaceShaderProgram = function (options) {
  48. if (defined(options.shaderProgram)) {
  49. options.shaderProgram.destroy();
  50. }
  51. return this.getShaderProgram(options);
  52. };
  53. function toSortedJson(dictionary) {
  54. const sortedKeys = Object.keys(dictionary).sort();
  55. return JSON.stringify(dictionary, sortedKeys);
  56. }
  57. /**
  58. * Returns a shader program from the cache, or creates and caches a new shader program,
  59. * given the GLSL vertex and fragment shader source and attribute locations.
  60. *
  61. * @param {object} options Object with the following properties:
  62. * @param {string|ShaderSource} options.vertexShaderSource The GLSL source for the vertex shader.
  63. * @param {string|ShaderSource} options.fragmentShaderSource The GLSL source for the fragment shader.
  64. * @param {object} options.attributeLocations Indices for the attribute inputs to the vertex shader.
  65. *
  66. * @returns {ShaderProgram} The cached or newly created shader program.
  67. */
  68. ShaderCache.prototype.getShaderProgram = function (options) {
  69. // convert shaders which are provided as strings into ShaderSource objects
  70. // because ShaderSource handles all the automatic including of built-in functions, etc.
  71. let vertexShaderSource = options.vertexShaderSource;
  72. let fragmentShaderSource = options.fragmentShaderSource;
  73. const attributeLocations = options.attributeLocations;
  74. if (typeof vertexShaderSource === "string") {
  75. vertexShaderSource = new ShaderSource({
  76. sources: [vertexShaderSource],
  77. });
  78. }
  79. if (typeof fragmentShaderSource === "string") {
  80. fragmentShaderSource = new ShaderSource({
  81. sources: [fragmentShaderSource],
  82. });
  83. }
  84. // Since ShaderSource.createCombinedXxxShader() can be expensive, use a
  85. // simpler key for caching. This way, the function does not have to be called
  86. // for each cache lookup.
  87. const vertexShaderKey = vertexShaderSource.getCacheKey();
  88. const fragmentShaderKey = fragmentShaderSource.getCacheKey();
  89. // Sort the keys in the JSON to ensure a consistent order
  90. const attributeLocationKey = defined(attributeLocations)
  91. ? toSortedJson(attributeLocations)
  92. : "";
  93. const keyword = `${vertexShaderKey}:${fragmentShaderKey}:${attributeLocationKey}`;
  94. let cachedShader;
  95. if (defined(this._shaders[keyword])) {
  96. cachedShader = this._shaders[keyword];
  97. // No longer want to release this if it was previously released.
  98. delete this._shadersToRelease[keyword];
  99. } else {
  100. const context = this._context;
  101. const vertexShaderText = vertexShaderSource.createCombinedVertexShader(
  102. context
  103. );
  104. const fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(
  105. context
  106. );
  107. const shaderProgram = new ShaderProgram({
  108. gl: context._gl,
  109. logShaderCompilation: context.logShaderCompilation,
  110. debugShaders: context.debugShaders,
  111. vertexShaderSource: vertexShaderSource,
  112. vertexShaderText: vertexShaderText,
  113. fragmentShaderSource: fragmentShaderSource,
  114. fragmentShaderText: fragmentShaderText,
  115. attributeLocations: attributeLocations,
  116. });
  117. cachedShader = {
  118. cache: this,
  119. shaderProgram: shaderProgram,
  120. keyword: keyword,
  121. derivedKeywords: [],
  122. count: 0,
  123. };
  124. // A shader can't be in more than one cache.
  125. shaderProgram._cachedShader = cachedShader;
  126. this._shaders[keyword] = cachedShader;
  127. ++this._numberOfShaders;
  128. }
  129. ++cachedShader.count;
  130. return cachedShader.shaderProgram;
  131. };
  132. ShaderCache.prototype.replaceDerivedShaderProgram = function (
  133. shaderProgram,
  134. keyword,
  135. options
  136. ) {
  137. const cachedShader = shaderProgram._cachedShader;
  138. const derivedKeyword = keyword + cachedShader.keyword;
  139. const cachedDerivedShader = this._shaders[derivedKeyword];
  140. if (defined(cachedDerivedShader)) {
  141. destroyShader(this, cachedDerivedShader);
  142. const index = cachedShader.derivedKeywords.indexOf(keyword);
  143. if (index > -1) {
  144. cachedShader.derivedKeywords.splice(index, 1);
  145. }
  146. }
  147. return this.createDerivedShaderProgram(shaderProgram, keyword, options);
  148. };
  149. ShaderCache.prototype.getDerivedShaderProgram = function (
  150. shaderProgram,
  151. keyword
  152. ) {
  153. const cachedShader = shaderProgram._cachedShader;
  154. const derivedKeyword = keyword + cachedShader.keyword;
  155. const cachedDerivedShader = this._shaders[derivedKeyword];
  156. if (!defined(cachedDerivedShader)) {
  157. return undefined;
  158. }
  159. return cachedDerivedShader.shaderProgram;
  160. };
  161. ShaderCache.prototype.createDerivedShaderProgram = function (
  162. shaderProgram,
  163. keyword,
  164. options
  165. ) {
  166. const cachedShader = shaderProgram._cachedShader;
  167. const derivedKeyword = keyword + cachedShader.keyword;
  168. let vertexShaderSource = options.vertexShaderSource;
  169. let fragmentShaderSource = options.fragmentShaderSource;
  170. const attributeLocations = options.attributeLocations;
  171. if (typeof vertexShaderSource === "string") {
  172. vertexShaderSource = new ShaderSource({
  173. sources: [vertexShaderSource],
  174. });
  175. }
  176. if (typeof fragmentShaderSource === "string") {
  177. fragmentShaderSource = new ShaderSource({
  178. sources: [fragmentShaderSource],
  179. });
  180. }
  181. const context = this._context;
  182. const vertexShaderText = vertexShaderSource.createCombinedVertexShader(
  183. context
  184. );
  185. const fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(
  186. context
  187. );
  188. const derivedShaderProgram = new ShaderProgram({
  189. gl: context._gl,
  190. logShaderCompilation: context.logShaderCompilation,
  191. debugShaders: context.debugShaders,
  192. vertexShaderSource: vertexShaderSource,
  193. vertexShaderText: vertexShaderText,
  194. fragmentShaderSource: fragmentShaderSource,
  195. fragmentShaderText: fragmentShaderText,
  196. attributeLocations: attributeLocations,
  197. });
  198. const derivedCachedShader = {
  199. cache: this,
  200. shaderProgram: derivedShaderProgram,
  201. keyword: derivedKeyword,
  202. derivedKeywords: [],
  203. count: 0,
  204. };
  205. cachedShader.derivedKeywords.push(keyword);
  206. derivedShaderProgram._cachedShader = derivedCachedShader;
  207. this._shaders[derivedKeyword] = derivedCachedShader;
  208. return derivedShaderProgram;
  209. };
  210. function destroyShader(cache, cachedShader) {
  211. const derivedKeywords = cachedShader.derivedKeywords;
  212. const length = derivedKeywords.length;
  213. for (let i = 0; i < length; ++i) {
  214. const keyword = derivedKeywords[i] + cachedShader.keyword;
  215. const derivedCachedShader = cache._shaders[keyword];
  216. destroyShader(cache, derivedCachedShader);
  217. }
  218. delete cache._shaders[cachedShader.keyword];
  219. cachedShader.shaderProgram.finalDestroy();
  220. }
  221. ShaderCache.prototype.destroyReleasedShaderPrograms = function () {
  222. const shadersToRelease = this._shadersToRelease;
  223. for (const keyword in shadersToRelease) {
  224. if (shadersToRelease.hasOwnProperty(keyword)) {
  225. const cachedShader = shadersToRelease[keyword];
  226. destroyShader(this, cachedShader);
  227. --this._numberOfShaders;
  228. }
  229. }
  230. this._shadersToRelease = {};
  231. };
  232. ShaderCache.prototype.releaseShaderProgram = function (shaderProgram) {
  233. if (defined(shaderProgram)) {
  234. const cachedShader = shaderProgram._cachedShader;
  235. if (cachedShader && --cachedShader.count === 0) {
  236. this._shadersToRelease[cachedShader.keyword] = cachedShader;
  237. }
  238. }
  239. };
  240. ShaderCache.prototype.isDestroyed = function () {
  241. return false;
  242. };
  243. ShaderCache.prototype.destroy = function () {
  244. const shaders = this._shaders;
  245. for (const keyword in shaders) {
  246. if (shaders.hasOwnProperty(keyword)) {
  247. shaders[keyword].shaderProgram.finalDestroy();
  248. }
  249. }
  250. return destroyObject(this);
  251. };
  252. export default ShaderCache;