webgpuCacheBindGroups.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /* eslint-disable babylonjs/available */
  2. /* eslint-disable jsdoc/require-jsdoc */
  3. import { Logger } from "../../Misc/logger.js";
  4. class WebGPUBindGroupCacheNode {
  5. constructor() {
  6. this.values = {};
  7. }
  8. }
  9. /** @internal */
  10. export class WebGPUCacheBindGroups {
  11. static get Statistics() {
  12. return {
  13. totalCreated: WebGPUCacheBindGroups.NumBindGroupsCreatedTotal,
  14. lastFrameCreated: WebGPUCacheBindGroups.NumBindGroupsCreatedLastFrame,
  15. lookupLastFrame: WebGPUCacheBindGroups.NumBindGroupsLookupLastFrame,
  16. noLookupLastFrame: WebGPUCacheBindGroups.NumBindGroupsNoLookupLastFrame,
  17. };
  18. }
  19. static ResetCache() {
  20. WebGPUCacheBindGroups._Cache = new WebGPUBindGroupCacheNode();
  21. WebGPUCacheBindGroups.NumBindGroupsCreatedTotal = 0;
  22. WebGPUCacheBindGroups.NumBindGroupsCreatedLastFrame = 0;
  23. WebGPUCacheBindGroups.NumBindGroupsLookupLastFrame = 0;
  24. WebGPUCacheBindGroups.NumBindGroupsNoLookupLastFrame = 0;
  25. WebGPUCacheBindGroups._NumBindGroupsCreatedCurrentFrame = 0;
  26. WebGPUCacheBindGroups._NumBindGroupsLookupCurrentFrame = 0;
  27. WebGPUCacheBindGroups._NumBindGroupsNoLookupCurrentFrame = 0;
  28. }
  29. constructor(device, cacheSampler, engine) {
  30. this.disabled = false;
  31. this._device = device;
  32. this._cacheSampler = cacheSampler;
  33. this._engine = engine;
  34. }
  35. endFrame() {
  36. WebGPUCacheBindGroups.NumBindGroupsCreatedLastFrame = WebGPUCacheBindGroups._NumBindGroupsCreatedCurrentFrame;
  37. WebGPUCacheBindGroups.NumBindGroupsLookupLastFrame = WebGPUCacheBindGroups._NumBindGroupsLookupCurrentFrame;
  38. WebGPUCacheBindGroups.NumBindGroupsNoLookupLastFrame = WebGPUCacheBindGroups._NumBindGroupsNoLookupCurrentFrame;
  39. WebGPUCacheBindGroups._NumBindGroupsCreatedCurrentFrame = 0;
  40. WebGPUCacheBindGroups._NumBindGroupsLookupCurrentFrame = 0;
  41. WebGPUCacheBindGroups._NumBindGroupsNoLookupCurrentFrame = 0;
  42. }
  43. /**
  44. * Cache is currently based on the uniform/storage buffers, samplers and textures used by the binding groups.
  45. * Note that all uniform buffers have an offset of 0 in Babylon and we don't have a use case where we would have the same buffer used with different capacity values:
  46. * that means we don't need to factor in the offset/size of the buffer in the cache, only the id
  47. * @param webgpuPipelineContext
  48. * @param drawContext
  49. * @param materialContext
  50. * @returns a bind group array
  51. */
  52. getBindGroups(webgpuPipelineContext, drawContext, materialContext) {
  53. let bindGroups = undefined;
  54. let node = WebGPUCacheBindGroups._Cache;
  55. const cacheIsDisabled = this.disabled || materialContext.forceBindGroupCreation;
  56. if (!cacheIsDisabled) {
  57. if (!drawContext.isDirty(materialContext.updateId) && !materialContext.isDirty) {
  58. WebGPUCacheBindGroups._NumBindGroupsNoLookupCurrentFrame++;
  59. return drawContext.bindGroups;
  60. }
  61. for (const bufferName of webgpuPipelineContext.shaderProcessingContext.bufferNames) {
  62. const uboId = drawContext.buffers[bufferName]?.uniqueId ?? 0;
  63. let nextNode = node.values[uboId];
  64. if (!nextNode) {
  65. nextNode = new WebGPUBindGroupCacheNode();
  66. node.values[uboId] = nextNode;
  67. }
  68. node = nextNode;
  69. }
  70. for (const samplerName of webgpuPipelineContext.shaderProcessingContext.samplerNames) {
  71. const samplerHashCode = materialContext.samplers[samplerName]?.hashCode ?? 0;
  72. let nextNode = node.values[samplerHashCode];
  73. if (!nextNode) {
  74. nextNode = new WebGPUBindGroupCacheNode();
  75. node.values[samplerHashCode] = nextNode;
  76. }
  77. node = nextNode;
  78. }
  79. for (const textureName of webgpuPipelineContext.shaderProcessingContext.textureNames) {
  80. const textureId = materialContext.textures[textureName]?.texture?.uniqueId ?? 0;
  81. let nextNode = node.values[textureId];
  82. if (!nextNode) {
  83. nextNode = new WebGPUBindGroupCacheNode();
  84. node.values[textureId] = nextNode;
  85. }
  86. node = nextNode;
  87. }
  88. bindGroups = node.bindGroups;
  89. }
  90. drawContext.resetIsDirty(materialContext.updateId);
  91. materialContext.isDirty = false;
  92. if (bindGroups) {
  93. drawContext.bindGroups = bindGroups;
  94. WebGPUCacheBindGroups._NumBindGroupsLookupCurrentFrame++;
  95. return bindGroups;
  96. }
  97. bindGroups = [];
  98. drawContext.bindGroups = bindGroups;
  99. if (!cacheIsDisabled) {
  100. node.bindGroups = bindGroups;
  101. }
  102. WebGPUCacheBindGroups.NumBindGroupsCreatedTotal++;
  103. WebGPUCacheBindGroups._NumBindGroupsCreatedCurrentFrame++;
  104. const bindGroupLayouts = webgpuPipelineContext.bindGroupLayouts[materialContext.textureState];
  105. for (let i = 0; i < webgpuPipelineContext.shaderProcessingContext.bindGroupLayoutEntries.length; i++) {
  106. const setDefinition = webgpuPipelineContext.shaderProcessingContext.bindGroupLayoutEntries[i];
  107. const entries = webgpuPipelineContext.shaderProcessingContext.bindGroupEntries[i];
  108. for (let j = 0; j < setDefinition.length; j++) {
  109. const entry = webgpuPipelineContext.shaderProcessingContext.bindGroupLayoutEntries[i][j];
  110. const entryInfo = webgpuPipelineContext.shaderProcessingContext.bindGroupLayoutEntryInfo[i][entry.binding];
  111. const name = entryInfo.nameInArrayOfTexture ?? entryInfo.name;
  112. if (entry.sampler) {
  113. const bindingInfo = materialContext.samplers[name];
  114. if (bindingInfo) {
  115. const sampler = bindingInfo.sampler;
  116. if (!sampler) {
  117. if (this._engine.dbgSanityChecks) {
  118. Logger.Error(`Trying to bind a null sampler! entry=${JSON.stringify(entry)}, name=${name}, bindingInfo=${JSON.stringify(bindingInfo, (key, value) => (key === "texture" ? "<no dump>" : value))}, materialContext.uniqueId=${materialContext.uniqueId}`, 50);
  119. }
  120. continue;
  121. }
  122. entries[j].resource = this._cacheSampler.getSampler(sampler, false, bindingInfo.hashCode, sampler.label);
  123. }
  124. else {
  125. Logger.Error(`Sampler "${name}" could not be bound. entry=${JSON.stringify(entry)}, materialContext=${JSON.stringify(materialContext, (key, value) => key === "texture" || key === "sampler" ? "<no dump>" : value)}`, 50);
  126. }
  127. }
  128. else if (entry.texture || entry.storageTexture) {
  129. const bindingInfo = materialContext.textures[name];
  130. if (bindingInfo) {
  131. if (this._engine.dbgSanityChecks && bindingInfo.texture === null) {
  132. Logger.Error(`Trying to bind a null texture! entry=${JSON.stringify(entry)}, bindingInfo=${JSON.stringify(bindingInfo, (key, value) => key === "texture" ? "<no dump>" : value)}, materialContext.uniqueId=${materialContext.uniqueId}`, 50);
  133. continue;
  134. }
  135. const hardwareTexture = bindingInfo.texture._hardwareTexture;
  136. if (this._engine.dbgSanityChecks &&
  137. (!hardwareTexture || (entry.texture && !hardwareTexture.view) || (entry.storageTexture && !hardwareTexture.viewForWriting))) {
  138. Logger.Error(`Trying to bind a null gpu texture or view! entry=${JSON.stringify(entry)}, name=${name}, bindingInfo=${JSON.stringify(bindingInfo, (key, value) => (key === "texture" ? "<no dump>" : value))}, isReady=${bindingInfo.texture?.isReady}, materialContext.uniqueId=${materialContext.uniqueId}`, 50);
  139. continue;
  140. }
  141. entries[j].resource = entry.storageTexture ? hardwareTexture.viewForWriting : hardwareTexture.view;
  142. }
  143. else {
  144. Logger.Error(`Texture "${name}" could not be bound. entry=${JSON.stringify(entry)}, materialContext=${JSON.stringify(materialContext, (key, value) => key === "texture" || key === "sampler" ? "<no dump>" : value)}`, 50);
  145. }
  146. }
  147. else if (entry.externalTexture) {
  148. const bindingInfo = materialContext.textures[name];
  149. if (bindingInfo) {
  150. if (this._engine.dbgSanityChecks && bindingInfo.texture === null) {
  151. Logger.Error(`Trying to bind a null external texture! entry=${JSON.stringify(entry)}, name=${name}, bindingInfo=${JSON.stringify(bindingInfo, (key, value) => (key === "texture" ? "<no dump>" : value))}, materialContext.uniqueId=${materialContext.uniqueId}`, 50);
  152. continue;
  153. }
  154. const externalTexture = bindingInfo.texture.underlyingResource;
  155. if (this._engine.dbgSanityChecks && !externalTexture) {
  156. Logger.Error(`Trying to bind a null gpu external texture! entry=${JSON.stringify(entry)}, name=${name}, bindingInfo=${JSON.stringify(bindingInfo, (key, value) => (key === "texture" ? "<no dump>" : value))}, isReady=${bindingInfo.texture?.isReady}, materialContext.uniqueId=${materialContext.uniqueId}`, 50);
  157. continue;
  158. }
  159. entries[j].resource = this._device.importExternalTexture({ source: externalTexture });
  160. }
  161. else {
  162. Logger.Error(`Texture "${name}" could not be bound. entry=${JSON.stringify(entry)}, materialContext=${JSON.stringify(materialContext, (key, value) => key === "texture" || key === "sampler" ? "<no dump>" : value)}`, 50);
  163. }
  164. }
  165. else if (entry.buffer) {
  166. const dataBuffer = drawContext.buffers[name];
  167. if (dataBuffer) {
  168. const webgpuBuffer = dataBuffer.underlyingResource;
  169. entries[j].resource.buffer = webgpuBuffer;
  170. entries[j].resource.size = dataBuffer.capacity;
  171. }
  172. else {
  173. Logger.Error(`Can't find buffer "${name}". entry=${JSON.stringify(entry)}, buffers=${JSON.stringify(drawContext.buffers)}, drawContext.uniqueId=${drawContext.uniqueId}`, 50);
  174. }
  175. }
  176. }
  177. const groupLayout = bindGroupLayouts[i];
  178. bindGroups[i] = this._device.createBindGroup({
  179. layout: groupLayout,
  180. entries,
  181. });
  182. }
  183. return bindGroups;
  184. }
  185. }
  186. WebGPUCacheBindGroups.NumBindGroupsCreatedTotal = 0;
  187. WebGPUCacheBindGroups.NumBindGroupsCreatedLastFrame = 0;
  188. WebGPUCacheBindGroups.NumBindGroupsLookupLastFrame = 0;
  189. WebGPUCacheBindGroups.NumBindGroupsNoLookupLastFrame = 0;
  190. WebGPUCacheBindGroups._Cache = new WebGPUBindGroupCacheNode();
  191. WebGPUCacheBindGroups._NumBindGroupsCreatedCurrentFrame = 0;
  192. WebGPUCacheBindGroups._NumBindGroupsLookupCurrentFrame = 0;
  193. WebGPUCacheBindGroups._NumBindGroupsNoLookupCurrentFrame = 0;
  194. //# sourceMappingURL=webgpuCacheBindGroups.js.map