materialPluginManager.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. import { Material } from "./material.js";
  2. import { MaterialPluginEvent } from "./materialPluginEvent.js";
  3. import { EngineStore } from "../Engines/engineStore.js";
  4. import { ShaderProcessor } from "../Engines/Processors/shaderProcessor.js";
  5. import { ShaderLanguage } from "./shaderLanguage.js";
  6. import { ShaderStore } from "../Engines/shaderStore.js";
  7. const rxOption = new RegExp("^([gimus]+)!");
  8. /**
  9. * Class that manages the plugins of a material
  10. * @since 5.0
  11. */
  12. export class MaterialPluginManager {
  13. /**
  14. * Creates a new instance of the plugin manager
  15. * @param material material that this manager will manage the plugins for
  16. */
  17. constructor(material) {
  18. /** @internal */
  19. this._plugins = [];
  20. this._activePlugins = [];
  21. this._activePluginsForExtraEvents = [];
  22. this._material = material;
  23. this._scene = material.getScene();
  24. this._engine = this._scene.getEngine();
  25. }
  26. /**
  27. * @internal
  28. */
  29. _addPlugin(plugin) {
  30. for (let i = 0; i < this._plugins.length; ++i) {
  31. if (this._plugins[i].name === plugin.name) {
  32. return false;
  33. }
  34. }
  35. if (this._material._uniformBufferLayoutBuilt) {
  36. // eslint-disable-next-line no-throw-literal
  37. throw `The plugin "${plugin.name}" can't be added to the material "${this._material.name}" because this material has already been used for rendering! Please add plugins to materials before any rendering with this material occurs.`;
  38. }
  39. const pluginClassName = plugin.getClassName();
  40. if (!MaterialPluginManager._MaterialPluginClassToMainDefine[pluginClassName]) {
  41. MaterialPluginManager._MaterialPluginClassToMainDefine[pluginClassName] = "MATERIALPLUGIN_" + ++MaterialPluginManager._MaterialPluginCounter;
  42. }
  43. this._material._callbackPluginEventGeneric = (id, info) => this._handlePluginEvent(id, info);
  44. this._plugins.push(plugin);
  45. this._plugins.sort((a, b) => a.priority - b.priority);
  46. this._codeInjectionPoints = {};
  47. const defineNamesFromPlugins = {};
  48. defineNamesFromPlugins[MaterialPluginManager._MaterialPluginClassToMainDefine[pluginClassName]] = {
  49. type: "boolean",
  50. default: true,
  51. };
  52. for (const plugin of this._plugins) {
  53. plugin.collectDefines(defineNamesFromPlugins);
  54. this._collectPointNames("vertex", plugin.getCustomCode("vertex"));
  55. this._collectPointNames("fragment", plugin.getCustomCode("fragment"));
  56. }
  57. this._defineNamesFromPlugins = defineNamesFromPlugins;
  58. return true;
  59. }
  60. /**
  61. * @internal
  62. */
  63. _activatePlugin(plugin) {
  64. if (this._activePlugins.indexOf(plugin) === -1) {
  65. this._activePlugins.push(plugin);
  66. this._activePlugins.sort((a, b) => a.priority - b.priority);
  67. this._material._callbackPluginEventIsReadyForSubMesh = this._handlePluginEventIsReadyForSubMesh.bind(this);
  68. this._material._callbackPluginEventPrepareDefinesBeforeAttributes = this._handlePluginEventPrepareDefinesBeforeAttributes.bind(this);
  69. this._material._callbackPluginEventPrepareDefines = this._handlePluginEventPrepareDefines.bind(this);
  70. this._material._callbackPluginEventBindForSubMesh = this._handlePluginEventBindForSubMesh.bind(this);
  71. if (plugin.registerForExtraEvents) {
  72. this._activePluginsForExtraEvents.push(plugin);
  73. this._activePluginsForExtraEvents.sort((a, b) => a.priority - b.priority);
  74. this._material._callbackPluginEventHasRenderTargetTextures = this._handlePluginEventHasRenderTargetTextures.bind(this);
  75. this._material._callbackPluginEventFillRenderTargetTextures = this._handlePluginEventFillRenderTargetTextures.bind(this);
  76. this._material._callbackPluginEventHardBindForSubMesh = this._handlePluginEventHardBindForSubMesh.bind(this);
  77. }
  78. }
  79. }
  80. /**
  81. * Gets a plugin from the list of plugins managed by this manager
  82. * @param name name of the plugin
  83. * @returns the plugin if found, else null
  84. */
  85. getPlugin(name) {
  86. for (let i = 0; i < this._plugins.length; ++i) {
  87. if (this._plugins[i].name === name) {
  88. return this._plugins[i];
  89. }
  90. }
  91. return null;
  92. }
  93. _handlePluginEventIsReadyForSubMesh(eventData) {
  94. let isReady = true;
  95. for (const plugin of this._activePlugins) {
  96. isReady = isReady && plugin.isReadyForSubMesh(eventData.defines, this._scene, this._engine, eventData.subMesh);
  97. }
  98. eventData.isReadyForSubMesh = isReady;
  99. }
  100. _handlePluginEventPrepareDefinesBeforeAttributes(eventData) {
  101. for (const plugin of this._activePlugins) {
  102. plugin.prepareDefinesBeforeAttributes(eventData.defines, this._scene, eventData.mesh);
  103. }
  104. }
  105. _handlePluginEventPrepareDefines(eventData) {
  106. for (const plugin of this._activePlugins) {
  107. plugin.prepareDefines(eventData.defines, this._scene, eventData.mesh);
  108. }
  109. }
  110. _handlePluginEventHardBindForSubMesh(eventData) {
  111. for (const plugin of this._activePluginsForExtraEvents) {
  112. plugin.hardBindForSubMesh(this._material._uniformBuffer, this._scene, this._engine, eventData.subMesh);
  113. }
  114. }
  115. _handlePluginEventBindForSubMesh(eventData) {
  116. for (const plugin of this._activePlugins) {
  117. plugin.bindForSubMesh(this._material._uniformBuffer, this._scene, this._engine, eventData.subMesh);
  118. }
  119. }
  120. _handlePluginEventHasRenderTargetTextures(eventData) {
  121. let hasRenderTargetTextures = false;
  122. for (const plugin of this._activePluginsForExtraEvents) {
  123. hasRenderTargetTextures = plugin.hasRenderTargetTextures();
  124. if (hasRenderTargetTextures) {
  125. break;
  126. }
  127. }
  128. eventData.hasRenderTargetTextures = hasRenderTargetTextures;
  129. }
  130. _handlePluginEventFillRenderTargetTextures(eventData) {
  131. for (const plugin of this._activePluginsForExtraEvents) {
  132. plugin.fillRenderTargetTextures(eventData.renderTargets);
  133. }
  134. }
  135. _handlePluginEvent(id, info) {
  136. switch (id) {
  137. case MaterialPluginEvent.GetActiveTextures: {
  138. const eventData = info;
  139. for (const plugin of this._activePlugins) {
  140. plugin.getActiveTextures(eventData.activeTextures);
  141. }
  142. break;
  143. }
  144. case MaterialPluginEvent.GetAnimatables: {
  145. const eventData = info;
  146. for (const plugin of this._activePlugins) {
  147. plugin.getAnimatables(eventData.animatables);
  148. }
  149. break;
  150. }
  151. case MaterialPluginEvent.HasTexture: {
  152. const eventData = info;
  153. let hasTexture = false;
  154. for (const plugin of this._activePlugins) {
  155. hasTexture = plugin.hasTexture(eventData.texture);
  156. if (hasTexture) {
  157. break;
  158. }
  159. }
  160. eventData.hasTexture = hasTexture;
  161. break;
  162. }
  163. case MaterialPluginEvent.Disposed: {
  164. const eventData = info;
  165. for (const plugin of this._plugins) {
  166. plugin.dispose(eventData.forceDisposeTextures);
  167. }
  168. break;
  169. }
  170. case MaterialPluginEvent.GetDefineNames: {
  171. const eventData = info;
  172. eventData.defineNames = this._defineNamesFromPlugins;
  173. break;
  174. }
  175. case MaterialPluginEvent.PrepareEffect: {
  176. const eventData = info;
  177. for (const plugin of this._activePlugins) {
  178. eventData.fallbackRank = plugin.addFallbacks(eventData.defines, eventData.fallbacks, eventData.fallbackRank);
  179. plugin.getAttributes(eventData.attributes, this._scene, eventData.mesh);
  180. }
  181. if (this._uniformList.length > 0) {
  182. eventData.uniforms.push(...this._uniformList);
  183. }
  184. if (this._samplerList.length > 0) {
  185. eventData.samplers.push(...this._samplerList);
  186. }
  187. if (this._uboList.length > 0) {
  188. eventData.uniformBuffersNames.push(...this._uboList);
  189. }
  190. eventData.customCode = this._injectCustomCode(eventData, eventData.customCode);
  191. break;
  192. }
  193. case MaterialPluginEvent.PrepareUniformBuffer: {
  194. const eventData = info;
  195. this._uboDeclaration = "";
  196. this._vertexDeclaration = "";
  197. this._fragmentDeclaration = "";
  198. this._uniformList = [];
  199. this._samplerList = [];
  200. this._uboList = [];
  201. for (const plugin of this._plugins) {
  202. const uniforms = plugin.getUniforms();
  203. if (uniforms) {
  204. if (uniforms.ubo) {
  205. for (const uniform of uniforms.ubo) {
  206. if (uniform.size && uniform.type) {
  207. const arraySize = uniform.arraySize ?? 0;
  208. eventData.ubo.addUniform(uniform.name, uniform.size, arraySize);
  209. this._uboDeclaration += `${uniform.type} ${uniform.name}${arraySize > 0 ? `[${arraySize}]` : ""};\n`;
  210. }
  211. this._uniformList.push(uniform.name);
  212. }
  213. }
  214. if (uniforms.vertex) {
  215. this._vertexDeclaration += uniforms.vertex + "\n";
  216. }
  217. if (uniforms.fragment) {
  218. this._fragmentDeclaration += uniforms.fragment + "\n";
  219. }
  220. }
  221. plugin.getSamplers(this._samplerList);
  222. plugin.getUniformBuffersNames(this._uboList);
  223. }
  224. break;
  225. }
  226. }
  227. }
  228. _collectPointNames(shaderType, customCode) {
  229. if (!customCode) {
  230. return;
  231. }
  232. for (const pointName in customCode) {
  233. if (!this._codeInjectionPoints[shaderType]) {
  234. this._codeInjectionPoints[shaderType] = {};
  235. }
  236. this._codeInjectionPoints[shaderType][pointName] = true;
  237. }
  238. }
  239. _injectCustomCode(eventData, existingCallback) {
  240. return (shaderType, code) => {
  241. if (existingCallback) {
  242. code = existingCallback(shaderType, code);
  243. }
  244. if (this._uboDeclaration) {
  245. code = code.replace("#define ADDITIONAL_UBO_DECLARATION", this._uboDeclaration);
  246. }
  247. if (this._vertexDeclaration) {
  248. code = code.replace("#define ADDITIONAL_VERTEX_DECLARATION", this._vertexDeclaration);
  249. }
  250. if (this._fragmentDeclaration) {
  251. code = code.replace("#define ADDITIONAL_FRAGMENT_DECLARATION", this._fragmentDeclaration);
  252. }
  253. const points = this._codeInjectionPoints?.[shaderType];
  254. if (!points) {
  255. return code;
  256. }
  257. let processorOptions = null;
  258. for (let pointName in points) {
  259. let injectedCode = "";
  260. for (const plugin of this._activePlugins) {
  261. let customCode = plugin.getCustomCode(shaderType)?.[pointName];
  262. if (!customCode) {
  263. continue;
  264. }
  265. if (plugin.resolveIncludes) {
  266. if (processorOptions === null) {
  267. const shaderLanguage = ShaderLanguage.GLSL;
  268. processorOptions = {
  269. defines: [],
  270. indexParameters: eventData.indexParameters,
  271. isFragment: false,
  272. shouldUseHighPrecisionShader: this._engine._shouldUseHighPrecisionShader,
  273. processor: undefined,
  274. supportsUniformBuffers: this._engine.supportsUniformBuffers,
  275. shadersRepository: ShaderStore.GetShadersRepository(shaderLanguage),
  276. includesShadersStore: ShaderStore.GetIncludesShadersStore(shaderLanguage),
  277. version: undefined,
  278. platformName: this._engine.shaderPlatformName,
  279. processingContext: undefined,
  280. isNDCHalfZRange: this._engine.isNDCHalfZRange,
  281. useReverseDepthBuffer: this._engine.useReverseDepthBuffer,
  282. processCodeAfterIncludes: undefined, // not used by _ProcessIncludes
  283. };
  284. }
  285. processorOptions.isFragment = shaderType === "fragment";
  286. ShaderProcessor._ProcessIncludes(customCode, processorOptions, (code) => (customCode = code));
  287. }
  288. injectedCode += customCode + "\n";
  289. }
  290. if (injectedCode.length > 0) {
  291. if (pointName.charAt(0) === "!") {
  292. // pointName is a regular expression
  293. pointName = pointName.substring(1);
  294. let regexFlags = "g";
  295. if (pointName.charAt(0) === "!") {
  296. // no flags
  297. regexFlags = "";
  298. pointName = pointName.substring(1);
  299. }
  300. else {
  301. // get the flag(s)
  302. const matchOption = rxOption.exec(pointName);
  303. if (matchOption && matchOption.length >= 2) {
  304. regexFlags = matchOption[1];
  305. pointName = pointName.substring(regexFlags.length + 1);
  306. }
  307. }
  308. if (regexFlags.indexOf("g") < 0) {
  309. // we force the "g" flag so that the regexp object is stateful!
  310. regexFlags += "g";
  311. }
  312. const sourceCode = code;
  313. const rx = new RegExp(pointName, regexFlags);
  314. let match = rx.exec(sourceCode);
  315. while (match !== null) {
  316. let newCode = injectedCode;
  317. for (let i = 0; i < match.length; ++i) {
  318. newCode = newCode.replace("$" + i, match[i]);
  319. }
  320. code = code.replace(match[0], newCode);
  321. match = rx.exec(sourceCode);
  322. }
  323. }
  324. else {
  325. const fullPointName = "#define " + pointName;
  326. code = code.replace(fullPointName, "\n" + injectedCode + "\n" + fullPointName);
  327. }
  328. }
  329. }
  330. return code;
  331. };
  332. }
  333. }
  334. /** Map a plugin class name to a #define name (used in the vertex/fragment shaders as a marker of the plugin usage) */
  335. MaterialPluginManager._MaterialPluginClassToMainDefine = {};
  336. MaterialPluginManager._MaterialPluginCounter = 0;
  337. (() => {
  338. EngineStore.OnEnginesDisposedObservable.add(() => {
  339. UnregisterAllMaterialPlugins();
  340. });
  341. })();
  342. const plugins = [];
  343. let inited = false;
  344. let observer = null;
  345. /**
  346. * Registers a new material plugin through a factory, or updates it. This makes the plugin available to all materials instantiated after its registration.
  347. * @param pluginName The plugin name
  348. * @param factory The factory function which allows to create the plugin
  349. */
  350. // eslint-disable-next-line @typescript-eslint/naming-convention
  351. export function RegisterMaterialPlugin(pluginName, factory) {
  352. if (!inited) {
  353. observer = Material.OnEventObservable.add((material) => {
  354. for (const [, factory] of plugins) {
  355. factory(material);
  356. }
  357. }, MaterialPluginEvent.Created);
  358. inited = true;
  359. }
  360. const existing = plugins.filter(([name, _factory]) => name === pluginName);
  361. if (existing.length > 0) {
  362. existing[0][1] = factory;
  363. }
  364. else {
  365. plugins.push([pluginName, factory]);
  366. }
  367. }
  368. /**
  369. * Removes a material plugin from the list of global plugins.
  370. * @param pluginName The plugin name
  371. * @returns true if the plugin has been removed, else false
  372. */
  373. // eslint-disable-next-line @typescript-eslint/naming-convention
  374. export function UnregisterMaterialPlugin(pluginName) {
  375. for (let i = 0; i < plugins.length; ++i) {
  376. if (plugins[i][0] === pluginName) {
  377. plugins.splice(i, 1);
  378. if (plugins.length === 0) {
  379. UnregisterAllMaterialPlugins();
  380. }
  381. return true;
  382. }
  383. }
  384. return false;
  385. }
  386. /**
  387. * Clear the list of global material plugins
  388. */
  389. // eslint-disable-next-line @typescript-eslint/naming-convention
  390. export function UnregisterAllMaterialPlugins() {
  391. plugins.length = 0;
  392. inited = false;
  393. Material.OnEventObservable.remove(observer);
  394. observer = null;
  395. }
  396. //# sourceMappingURL=materialPluginManager.js.map