computeEffect.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. import { Logger } from "../Misc/logger.js";
  2. import { Observable } from "../Misc/observable.js";
  3. import { GetDOMTextContent, IsWindowObjectExist } from "../Misc/domManagement.js";
  4. import { ShaderProcessor } from "../Engines/Processors/shaderProcessor.js";
  5. import { ShaderStore } from "../Engines/shaderStore.js";
  6. import { ShaderLanguage } from "../Materials/shaderLanguage.js";
  7. /**
  8. * Effect wrapping a compute shader and let execute (dispatch) the shader
  9. */
  10. export class ComputeEffect {
  11. /**
  12. * Creates a compute effect that can be used to execute a compute shader
  13. * @param baseName Name of the effect
  14. * @param options Set of all options to create the effect
  15. * @param engine The engine the effect is created for
  16. * @param key Effect Key identifying uniquely compiled shader variants
  17. */
  18. constructor(baseName, options, engine, key = "") {
  19. /**
  20. * String container all the define statements that should be set on the shader.
  21. */
  22. this.defines = "";
  23. /**
  24. * Callback that will be called when the shader is compiled.
  25. */
  26. this.onCompiled = null;
  27. /**
  28. * Callback that will be called if an error occurs during shader compilation.
  29. */
  30. this.onError = null;
  31. /**
  32. * Unique ID of the effect.
  33. */
  34. this.uniqueId = 0;
  35. /**
  36. * Observable that will be called when the shader is compiled.
  37. * It is recommended to use executeWhenCompile() or to make sure that scene.isReady() is called to get this observable raised.
  38. */
  39. this.onCompileObservable = new Observable();
  40. /**
  41. * Observable that will be called if an error occurs during shader compilation.
  42. */
  43. this.onErrorObservable = new Observable();
  44. /**
  45. * Observable that will be called when effect is bound.
  46. */
  47. this.onBindObservable = new Observable();
  48. /**
  49. * @internal
  50. * Specifies if the effect was previously ready
  51. */
  52. this._wasPreviouslyReady = false;
  53. this._isReady = false;
  54. this._compilationError = "";
  55. /** @internal */
  56. this._key = "";
  57. this._computeSourceCodeOverride = "";
  58. /** @internal */
  59. this._pipelineContext = null;
  60. /** @internal */
  61. this._computeSourceCode = "";
  62. this._rawComputeSourceCode = "";
  63. this._shaderLanguage = ShaderLanguage.WGSL;
  64. this.name = baseName;
  65. this._key = key;
  66. this._engine = engine;
  67. this.uniqueId = ComputeEffect._UniqueIdSeed++;
  68. this.defines = options.defines ?? "";
  69. this.onError = options.onError;
  70. this.onCompiled = options.onCompiled;
  71. this._entryPoint = options.entryPoint ?? "main";
  72. this._shaderStore = ShaderStore.GetShadersStore(this._shaderLanguage);
  73. this._shaderRepository = ShaderStore.GetShadersRepository(this._shaderLanguage);
  74. this._includeShaderStore = ShaderStore.GetIncludesShadersStore(this._shaderLanguage);
  75. let computeSource;
  76. const hostDocument = IsWindowObjectExist() ? this._engine.getHostDocument() : null;
  77. if (typeof baseName === "string") {
  78. computeSource = baseName;
  79. }
  80. else if (baseName.computeSource) {
  81. computeSource = "source:" + baseName.computeSource;
  82. }
  83. else if (baseName.computeElement) {
  84. computeSource = hostDocument?.getElementById(baseName.computeElement) || baseName.computeElement;
  85. }
  86. else {
  87. computeSource = baseName.compute || baseName;
  88. }
  89. const processorOptions = {
  90. defines: this.defines.split("\n"),
  91. indexParameters: undefined,
  92. isFragment: false,
  93. shouldUseHighPrecisionShader: false,
  94. processor: null,
  95. supportsUniformBuffers: this._engine.supportsUniformBuffers,
  96. shadersRepository: this._shaderRepository,
  97. includesShadersStore: this._includeShaderStore,
  98. version: (this._engine.version * 100).toString(),
  99. platformName: this._engine.shaderPlatformName,
  100. processingContext: null,
  101. isNDCHalfZRange: this._engine.isNDCHalfZRange,
  102. useReverseDepthBuffer: this._engine.useReverseDepthBuffer,
  103. };
  104. this._loadShader(computeSource, "Compute", "", (computeCode) => {
  105. ShaderProcessor.Initialize(processorOptions);
  106. ShaderProcessor.PreProcess(computeCode, processorOptions, (migratedCommputeCode) => {
  107. this._rawComputeSourceCode = computeCode;
  108. if (options.processFinalCode) {
  109. migratedCommputeCode = options.processFinalCode(migratedCommputeCode);
  110. }
  111. const finalShaders = ShaderProcessor.Finalize(migratedCommputeCode, "", processorOptions);
  112. this._useFinalCode(finalShaders.vertexCode, baseName);
  113. }, this._engine);
  114. });
  115. }
  116. _useFinalCode(migratedCommputeCode, baseName) {
  117. if (baseName) {
  118. const compute = baseName.computeElement || baseName.compute || baseName.spectorName || baseName;
  119. this._computeSourceCode = "//#define SHADER_NAME compute:" + compute + "\n" + migratedCommputeCode;
  120. }
  121. else {
  122. this._computeSourceCode = migratedCommputeCode;
  123. }
  124. this._prepareEffect();
  125. }
  126. /**
  127. * Unique key for this effect
  128. */
  129. get key() {
  130. return this._key;
  131. }
  132. /**
  133. * If the effect has been compiled and prepared.
  134. * @returns if the effect is compiled and prepared.
  135. */
  136. isReady() {
  137. try {
  138. return this._isReadyInternal();
  139. }
  140. catch {
  141. return false;
  142. }
  143. }
  144. _isReadyInternal() {
  145. if (this._isReady) {
  146. return true;
  147. }
  148. if (this._pipelineContext) {
  149. return this._pipelineContext.isReady;
  150. }
  151. return false;
  152. }
  153. /**
  154. * The engine the effect was initialized with.
  155. * @returns the engine.
  156. */
  157. getEngine() {
  158. return this._engine;
  159. }
  160. /**
  161. * The pipeline context for this effect
  162. * @returns the associated pipeline context
  163. */
  164. getPipelineContext() {
  165. return this._pipelineContext;
  166. }
  167. /**
  168. * The error from the last compilation.
  169. * @returns the error string.
  170. */
  171. getCompilationError() {
  172. return this._compilationError;
  173. }
  174. /**
  175. * Adds a callback to the onCompiled observable and call the callback immediately if already ready.
  176. * @param func The callback to be used.
  177. */
  178. executeWhenCompiled(func) {
  179. if (this.isReady()) {
  180. func(this);
  181. return;
  182. }
  183. this.onCompileObservable.add((effect) => {
  184. func(effect);
  185. });
  186. if (!this._pipelineContext || this._pipelineContext.isAsync) {
  187. setTimeout(() => {
  188. this._checkIsReady(null);
  189. }, 16);
  190. }
  191. }
  192. _checkIsReady(previousPipelineContext) {
  193. try {
  194. if (this._isReadyInternal()) {
  195. return;
  196. }
  197. }
  198. catch (e) {
  199. this._processCompilationErrors(e, previousPipelineContext);
  200. return;
  201. }
  202. setTimeout(() => {
  203. this._checkIsReady(previousPipelineContext);
  204. }, 16);
  205. }
  206. _loadShader(shader, key, optionalKey, callback) {
  207. if (typeof HTMLElement !== "undefined") {
  208. // DOM element ?
  209. if (shader instanceof HTMLElement) {
  210. const shaderCode = GetDOMTextContent(shader);
  211. callback(shaderCode);
  212. return;
  213. }
  214. }
  215. // Direct source ?
  216. if (shader.substr(0, 7) === "source:") {
  217. callback(shader.substr(7));
  218. return;
  219. }
  220. // Base64 encoded ?
  221. if (shader.substr(0, 7) === "base64:") {
  222. const shaderBinary = window.atob(shader.substr(7));
  223. callback(shaderBinary);
  224. return;
  225. }
  226. // Is in local store ?
  227. if (this._shaderStore[shader + key + "Shader"]) {
  228. callback(this._shaderStore[shader + key + "Shader"]);
  229. return;
  230. }
  231. if (optionalKey && this._shaderStore[shader + optionalKey + "Shader"]) {
  232. callback(this._shaderStore[shader + optionalKey + "Shader"]);
  233. return;
  234. }
  235. let shaderUrl;
  236. if (shader[0] === "." || shader[0] === "/" || shader.indexOf("http") > -1) {
  237. shaderUrl = shader;
  238. }
  239. else {
  240. shaderUrl = this._shaderRepository + shader;
  241. }
  242. this._engine._loadFile(shaderUrl + "." + key.toLowerCase() + ".fx", callback);
  243. }
  244. /**
  245. * Gets the compute shader source code of this effect
  246. */
  247. get computeSourceCode() {
  248. return this._computeSourceCodeOverride ? this._computeSourceCodeOverride : this._pipelineContext?._getComputeShaderCode() ?? this._computeSourceCode;
  249. }
  250. /**
  251. * Gets the compute shader source code before it has been processed by the preprocessor
  252. */
  253. get rawComputeSourceCode() {
  254. return this._rawComputeSourceCode;
  255. }
  256. /**
  257. * Prepares the effect
  258. * @internal
  259. */
  260. _prepareEffect() {
  261. const defines = this.defines;
  262. const previousPipelineContext = this._pipelineContext;
  263. this._isReady = false;
  264. try {
  265. const engine = this._engine;
  266. this._pipelineContext = engine.createComputePipelineContext();
  267. this._pipelineContext._name = this._key;
  268. engine._prepareComputePipelineContext(this._pipelineContext, this._computeSourceCodeOverride ? this._computeSourceCodeOverride : this._computeSourceCode, this._rawComputeSourceCode, this._computeSourceCodeOverride ? null : defines, this._entryPoint);
  269. engine._executeWhenComputeStateIsCompiled(this._pipelineContext, (messages) => {
  270. if (messages && messages.numErrors > 0) {
  271. this._processCompilationErrors(messages, previousPipelineContext);
  272. return;
  273. }
  274. this._compilationError = "";
  275. this._isReady = true;
  276. if (this.onCompiled) {
  277. this.onCompiled(this);
  278. }
  279. this.onCompileObservable.notifyObservers(this);
  280. this.onCompileObservable.clear();
  281. if (previousPipelineContext) {
  282. this.getEngine()._deleteComputePipelineContext(previousPipelineContext);
  283. }
  284. });
  285. if (this._pipelineContext.isAsync) {
  286. this._checkIsReady(previousPipelineContext);
  287. }
  288. }
  289. catch (e) {
  290. this._processCompilationErrors(e, previousPipelineContext);
  291. }
  292. }
  293. _processCompilationErrors(e, previousPipelineContext = null) {
  294. this._compilationError = "";
  295. Logger.Error("Unable to compile compute effect:");
  296. if (this.defines) {
  297. Logger.Error("Defines:\n" + this.defines);
  298. }
  299. if (ComputeEffect.LogShaderCodeOnCompilationError) {
  300. const code = this._pipelineContext?._getComputeShaderCode();
  301. if (code) {
  302. Logger.Error("Compute code:");
  303. Logger.Error(code);
  304. }
  305. }
  306. if (typeof e === "string") {
  307. this._compilationError = e;
  308. Logger.Error("Error: " + this._compilationError);
  309. }
  310. else {
  311. for (const message of e.messages) {
  312. let msg = "";
  313. if (message.line !== undefined) {
  314. msg += "Line " + message.line + ", ";
  315. }
  316. if (message.offset !== undefined) {
  317. msg += "Offset " + message.offset + ", ";
  318. }
  319. if (message.length !== undefined) {
  320. msg += "Length " + message.length + ", ";
  321. }
  322. msg += message.type + ": " + message.text;
  323. if (this._compilationError) {
  324. this._compilationError += "\n";
  325. }
  326. this._compilationError += msg;
  327. Logger.Error(msg);
  328. }
  329. }
  330. if (previousPipelineContext) {
  331. this._pipelineContext = previousPipelineContext;
  332. this._isReady = true;
  333. }
  334. if (this.onError) {
  335. this.onError(this, this._compilationError);
  336. }
  337. this.onErrorObservable.notifyObservers(this);
  338. }
  339. /**
  340. * Release all associated resources.
  341. **/
  342. dispose() {
  343. if (this._pipelineContext) {
  344. this._pipelineContext.dispose();
  345. }
  346. this._engine._releaseComputeEffect(this);
  347. }
  348. /**
  349. * This function will add a new compute shader to the shader store
  350. * @param name the name of the shader
  351. * @param computeShader compute shader content
  352. */
  353. static RegisterShader(name, computeShader) {
  354. ShaderStore.GetShadersStore(ShaderLanguage.WGSL)[`${name}ComputeShader`] = computeShader;
  355. }
  356. }
  357. ComputeEffect._UniqueIdSeed = 0;
  358. /**
  359. * Enable logging of the shader code when a compilation error occurs
  360. */
  361. ComputeEffect.LogShaderCodeOnCompilationError = true;
  362. //# sourceMappingURL=computeEffect.js.map