WebGPUBindingUtils.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. import {
  2. GPUTextureAspect, GPUTextureViewDimension, GPUTextureSampleType, GPUBufferBindingType, GPUStorageTextureAccess,
  3. GPUSamplerBindingType, GPUShaderStage
  4. } from './WebGPUConstants.js';
  5. import { FloatType, IntType, UnsignedIntType, Compatibility } from '../../../constants.js';
  6. import { NodeAccess } from '../../../nodes/core/constants.js';
  7. import { isTypedArray, error } from '../../../utils.js';
  8. /**
  9. * Class representing a WebGPU bind group layout.
  10. *
  11. * @private
  12. */
  13. class BindGroupLayout {
  14. /**
  15. * Constructs a new layout.
  16. *
  17. * @param {GPUBindGroupLayout} layoutGPU - A GPU Bind Group Layout.
  18. */
  19. constructor( layoutGPU ) {
  20. /**
  21. * The current GPUBindGroupLayout.
  22. *
  23. * @type {GPUBindGroupLayout}
  24. */
  25. this.layoutGPU = layoutGPU;
  26. /**
  27. * The number of bind groups that use this layout.
  28. *
  29. * @type {number}
  30. */
  31. this.usedTimes = 0;
  32. }
  33. }
  34. /**
  35. * A WebGPU backend utility module for managing bindings.
  36. *
  37. * When reading the documentation it's helpful to keep in mind that
  38. * all class definitions starting with 'GPU*' are modules from the
  39. * WebGPU API. So for example `BindGroup` is a class from the engine
  40. * whereas `GPUBindGroup` is a class from WebGPU.
  41. *
  42. * @private
  43. */
  44. class WebGPUBindingUtils {
  45. /**
  46. * Constructs a new utility object.
  47. *
  48. * @param {WebGPUBackend} backend - The WebGPU backend.
  49. */
  50. constructor( backend ) {
  51. /**
  52. * A reference to the WebGPU backend.
  53. *
  54. * @type {WebGPUBackend}
  55. */
  56. this.backend = backend;
  57. /**
  58. * A cache that maps combinations of layout entries to existing bind group layouts.
  59. *
  60. * @private
  61. * @type {Map<string, BindGroupLayout>}
  62. */
  63. this._bindGroupLayoutCache = new Map();
  64. }
  65. /**
  66. * Creates a GPU bind group layout for the given bind group.
  67. *
  68. * @param {BindGroup} bindGroup - The bind group.
  69. * @return {GPUBindGroupLayout} The GPU bind group layout.
  70. */
  71. createBindingsLayout( bindGroup ) {
  72. const backend = this.backend;
  73. const device = backend.device;
  74. const bindingsData = backend.get( bindGroup );
  75. // check if the the bind group already has a layout
  76. if ( bindingsData.layout ) {
  77. return bindingsData.layout.layoutGPU;
  78. }
  79. // if not, assing one
  80. const entries = this._createLayoutEntries( bindGroup );
  81. const bindGroupLayoutKey = JSON.stringify( entries );
  82. // try to find an existing layout in the cache
  83. let bindGroupLayout = this._bindGroupLayoutCache.get( bindGroupLayoutKey );
  84. // if not create a new one
  85. if ( bindGroupLayout === undefined ) {
  86. bindGroupLayout = new BindGroupLayout( device.createBindGroupLayout( { entries } ) );
  87. this._bindGroupLayoutCache.set( bindGroupLayoutKey, bindGroupLayout );
  88. }
  89. bindGroupLayout.usedTimes ++;
  90. bindingsData.layout = bindGroupLayout;
  91. bindingsData.layoutKey = bindGroupLayoutKey;
  92. return bindGroupLayout.layoutGPU;
  93. }
  94. /**
  95. * Creates bindings from the given bind group definition.
  96. *
  97. * @param {BindGroup} bindGroup - The bind group.
  98. * @param {Array<BindGroup>} bindings - Array of bind groups.
  99. * @param {number} cacheIndex - The cache index.
  100. * @param {number} version - The version.
  101. */
  102. createBindings( bindGroup, bindings, cacheIndex, version = 0 ) {
  103. const { backend } = this;
  104. const bindingsData = backend.get( bindGroup );
  105. // setup (static) binding layout and (dynamic) binding group
  106. const bindLayoutGPU = this.createBindingsLayout( bindGroup );
  107. let bindGroupGPU;
  108. if ( cacheIndex > 0 ) {
  109. if ( bindingsData.groups === undefined ) {
  110. bindingsData.groups = [];
  111. bindingsData.versions = [];
  112. }
  113. if ( bindingsData.versions[ cacheIndex ] === version ) {
  114. bindGroupGPU = bindingsData.groups[ cacheIndex ];
  115. }
  116. }
  117. if ( bindGroupGPU === undefined ) {
  118. bindGroupGPU = this.createBindGroup( bindGroup, bindLayoutGPU );
  119. if ( cacheIndex > 0 ) {
  120. bindingsData.groups[ cacheIndex ] = bindGroupGPU;
  121. bindingsData.versions[ cacheIndex ] = version;
  122. }
  123. }
  124. bindingsData.group = bindGroupGPU;
  125. }
  126. /**
  127. * Updates a buffer binding.
  128. *
  129. * @param {Buffer} binding - The buffer binding to update.
  130. */
  131. updateBinding( binding ) {
  132. const backend = this.backend;
  133. const device = backend.device;
  134. const array = binding.buffer; // cpu
  135. const buffer = backend.get( binding ).buffer; // gpu
  136. const updateRanges = binding.updateRanges;
  137. if ( updateRanges.length === 0 ) {
  138. device.queue.writeBuffer(
  139. buffer,
  140. 0,
  141. array,
  142. 0
  143. );
  144. } else {
  145. const isTyped = isTypedArray( array );
  146. const byteOffsetFactor = isTyped ? 1 : array.BYTES_PER_ELEMENT;
  147. for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
  148. const range = updateRanges[ i ];
  149. const dataOffset = range.start * byteOffsetFactor;
  150. const size = range.count * byteOffsetFactor;
  151. const bufferOffset = dataOffset * ( isTyped ? array.BYTES_PER_ELEMENT : 1 ); // bufferOffset is always in bytes
  152. device.queue.writeBuffer(
  153. buffer,
  154. bufferOffset,
  155. array,
  156. dataOffset,
  157. size
  158. );
  159. }
  160. }
  161. }
  162. /**
  163. * Creates a GPU bind group for the camera index.
  164. *
  165. * @param {Uint32Array} data - The index data.
  166. * @param {GPUBindGroupLayout} layoutGPU - The GPU bind group layout.
  167. * @return {GPUBindGroup} The GPU bind group.
  168. */
  169. createBindGroupIndex( data, layoutGPU ) {
  170. const backend = this.backend;
  171. const device = backend.device;
  172. const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
  173. const index = data[ 0 ];
  174. const buffer = device.createBuffer( {
  175. label: 'bindingCameraIndex_' + index,
  176. size: 16, // uint(4) * 4
  177. usage: usage
  178. } );
  179. device.queue.writeBuffer( buffer, 0, data, 0 );
  180. const entries = [ { binding: 0, resource: { buffer } } ];
  181. return device.createBindGroup( {
  182. label: 'bindGroupCameraIndex_' + index,
  183. layout: layoutGPU,
  184. entries
  185. } );
  186. }
  187. /**
  188. * Creates a GPU bind group for the given bind group and GPU layout.
  189. *
  190. * @param {BindGroup} bindGroup - The bind group.
  191. * @param {GPUBindGroupLayout} layoutGPU - The GPU bind group layout.
  192. * @return {GPUBindGroup} The GPU bind group.
  193. */
  194. createBindGroup( bindGroup, layoutGPU ) {
  195. const backend = this.backend;
  196. const device = backend.device;
  197. let bindingPoint = 0;
  198. const entriesGPU = [];
  199. for ( const binding of bindGroup.bindings ) {
  200. if ( binding.isUniformBuffer ) {
  201. const bindingData = backend.get( binding );
  202. if ( bindingData.buffer === undefined ) {
  203. const byteLength = binding.byteLength;
  204. const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
  205. const visibilities = [];
  206. if ( binding.visibility & GPUShaderStage.VERTEX ) {
  207. visibilities.push( 'vertex' );
  208. }
  209. if ( binding.visibility & GPUShaderStage.FRAGMENT ) {
  210. visibilities.push( 'fragment' );
  211. }
  212. if ( binding.visibility & GPUShaderStage.COMPUTE ) {
  213. visibilities.push( 'compute' );
  214. }
  215. const bufferVisibility = `(${visibilities.join( ',' )})`;
  216. const bufferGPU = device.createBuffer( {
  217. label: `bindingBuffer${binding.id}_${binding.name}_${bufferVisibility}`,
  218. size: byteLength,
  219. usage: usage
  220. } );
  221. bindingData.buffer = bufferGPU;
  222. }
  223. entriesGPU.push( { binding: bindingPoint, resource: { buffer: bindingData.buffer } } );
  224. } else if ( binding.isStorageBuffer ) {
  225. const buffer = backend.get( binding.attribute ).buffer;
  226. entriesGPU.push( { binding: bindingPoint, resource: { buffer: buffer } } );
  227. } else if ( binding.isSampledTexture ) {
  228. const textureData = backend.get( binding.texture );
  229. let resourceGPU;
  230. if ( textureData.externalTexture !== undefined ) {
  231. resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } );
  232. } else {
  233. const mipLevelCount = binding.store ? 1 : textureData.texture.mipLevelCount;
  234. const baseMipLevel = binding.store ? binding.mipLevel : 0;
  235. let propertyName = `view-${ textureData.texture.width }-${ textureData.texture.height }`;
  236. if ( textureData.texture.depthOrArrayLayers > 1 ) {
  237. propertyName += `-${ textureData.texture.depthOrArrayLayers }`;
  238. }
  239. propertyName += `-${ mipLevelCount }-${ baseMipLevel }`;
  240. resourceGPU = textureData[ propertyName ];
  241. if ( resourceGPU === undefined ) {
  242. const aspectGPU = GPUTextureAspect.All;
  243. let dimensionViewGPU;
  244. if ( binding.isSampledCubeTexture ) {
  245. dimensionViewGPU = GPUTextureViewDimension.Cube;
  246. } else if ( binding.isSampledTexture3D ) {
  247. dimensionViewGPU = GPUTextureViewDimension.ThreeD;
  248. } else if ( binding.texture.isArrayTexture || binding.texture.isDataArrayTexture || binding.texture.isCompressedArrayTexture ) {
  249. dimensionViewGPU = GPUTextureViewDimension.TwoDArray;
  250. } else {
  251. dimensionViewGPU = GPUTextureViewDimension.TwoD;
  252. }
  253. resourceGPU = textureData[ propertyName ] = textureData.texture.createView( { aspect: aspectGPU, dimension: dimensionViewGPU, mipLevelCount, baseMipLevel } );
  254. }
  255. }
  256. entriesGPU.push( { binding: bindingPoint, resource: resourceGPU } );
  257. } else if ( binding.isSampler ) {
  258. const textureGPU = backend.get( binding.texture );
  259. entriesGPU.push( { binding: bindingPoint, resource: textureGPU.sampler } );
  260. }
  261. bindingPoint ++;
  262. }
  263. return device.createBindGroup( {
  264. label: 'bindGroup_' + bindGroup.name,
  265. layout: layoutGPU,
  266. entries: entriesGPU
  267. } );
  268. }
  269. /**
  270. * Creates a GPU bind group layout entries for the given bind group.
  271. *
  272. * @private
  273. * @param {BindGroup} bindGroup - The bind group.
  274. * @return {Array<GPUBindGroupLayoutEntry>} The GPU bind group layout entries.
  275. */
  276. _createLayoutEntries( bindGroup ) {
  277. const entries = [];
  278. let index = 0;
  279. for ( const binding of bindGroup.bindings ) {
  280. const backend = this.backend;
  281. const bindingGPU = {
  282. binding: index,
  283. visibility: binding.visibility
  284. };
  285. if ( binding.isUniformBuffer || binding.isStorageBuffer ) {
  286. const buffer = {}; // GPUBufferBindingLayout
  287. if ( binding.isStorageBuffer ) {
  288. if ( binding.visibility & GPUShaderStage.COMPUTE ) {
  289. // compute
  290. if ( binding.access === NodeAccess.READ_WRITE || binding.access === NodeAccess.WRITE_ONLY ) {
  291. buffer.type = GPUBufferBindingType.Storage;
  292. } else {
  293. buffer.type = GPUBufferBindingType.ReadOnlyStorage;
  294. }
  295. } else {
  296. buffer.type = GPUBufferBindingType.ReadOnlyStorage;
  297. }
  298. }
  299. bindingGPU.buffer = buffer;
  300. } else if ( binding.isSampledTexture && binding.store ) {
  301. const storageTexture = {}; // GPUStorageTextureBindingLayout
  302. storageTexture.format = this.backend.get( binding.texture ).texture.format;
  303. const access = binding.access;
  304. if ( access === NodeAccess.READ_WRITE ) {
  305. storageTexture.access = GPUStorageTextureAccess.ReadWrite;
  306. } else if ( access === NodeAccess.WRITE_ONLY ) {
  307. storageTexture.access = GPUStorageTextureAccess.WriteOnly;
  308. } else {
  309. storageTexture.access = GPUStorageTextureAccess.ReadOnly;
  310. }
  311. if ( binding.texture.isArrayTexture ) {
  312. storageTexture.viewDimension = GPUTextureViewDimension.TwoDArray;
  313. } else if ( binding.texture.is3DTexture ) {
  314. storageTexture.viewDimension = GPUTextureViewDimension.ThreeD;
  315. }
  316. bindingGPU.storageTexture = storageTexture;
  317. } else if ( binding.isSampledTexture ) {
  318. const texture = {}; // GPUTextureBindingLayout
  319. const { primarySamples } = backend.utils.getTextureSampleData( binding.texture );
  320. if ( primarySamples > 1 ) {
  321. texture.multisampled = true;
  322. if ( ! binding.texture.isDepthTexture ) {
  323. texture.sampleType = GPUTextureSampleType.UnfilterableFloat;
  324. }
  325. }
  326. if ( binding.texture.isDepthTexture ) {
  327. if ( backend.compatibilityMode && binding.texture.compareFunction === null ) {
  328. texture.sampleType = GPUTextureSampleType.UnfilterableFloat;
  329. } else {
  330. texture.sampleType = GPUTextureSampleType.Depth;
  331. }
  332. } else if ( binding.texture.isDataTexture || binding.texture.isDataArrayTexture || binding.texture.isData3DTexture ) {
  333. const type = binding.texture.type;
  334. if ( type === IntType ) {
  335. texture.sampleType = GPUTextureSampleType.SInt;
  336. } else if ( type === UnsignedIntType ) {
  337. texture.sampleType = GPUTextureSampleType.UInt;
  338. } else if ( type === FloatType ) {
  339. if ( this.backend.hasFeature( 'float32-filterable' ) ) {
  340. texture.sampleType = GPUTextureSampleType.Float;
  341. } else {
  342. texture.sampleType = GPUTextureSampleType.UnfilterableFloat;
  343. }
  344. }
  345. }
  346. if ( binding.isSampledCubeTexture ) {
  347. texture.viewDimension = GPUTextureViewDimension.Cube;
  348. } else if ( binding.texture.isArrayTexture || binding.texture.isDataArrayTexture || binding.texture.isCompressedArrayTexture ) {
  349. texture.viewDimension = GPUTextureViewDimension.TwoDArray;
  350. } else if ( binding.isSampledTexture3D ) {
  351. texture.viewDimension = GPUTextureViewDimension.ThreeD;
  352. }
  353. bindingGPU.texture = texture;
  354. } else if ( binding.isSampler ) {
  355. const sampler = {}; // GPUSamplerBindingLayout
  356. if ( binding.texture.isDepthTexture ) {
  357. if ( binding.texture.compareFunction !== null && backend.hasCompatibility( Compatibility.TEXTURE_COMPARE ) ) {
  358. sampler.type = GPUSamplerBindingType.Comparison;
  359. } else {
  360. // Depth textures without compare must use non-filtering sampler
  361. sampler.type = GPUSamplerBindingType.NonFiltering;
  362. }
  363. }
  364. bindingGPU.sampler = sampler;
  365. } else {
  366. error( `WebGPUBindingUtils: Unsupported binding "${ binding }".` );
  367. }
  368. entries.push( bindingGPU );
  369. index ++;
  370. }
  371. return entries;
  372. }
  373. /**
  374. * Delete the data associated with a bind group.
  375. *
  376. * @param {BindGroup} bindGroup - The bind group.
  377. */
  378. deleteBindGroupData( bindGroup ) {
  379. const { backend } = this;
  380. const bindingsData = backend.get( bindGroup );
  381. if ( bindingsData.layout ) {
  382. bindingsData.layout.usedTimes --;
  383. if ( bindingsData.layout.usedTimes === 0 ) {
  384. this._bindGroupLayoutCache.delete( bindingsData.layoutKey );
  385. }
  386. bindingsData.layout = undefined;
  387. bindingsData.layoutKey = undefined;
  388. }
  389. }
  390. /**
  391. * Frees internal resources.
  392. */
  393. dispose() {
  394. this._bindGroupLayoutCache.clear();
  395. }
  396. }
  397. export default WebGPUBindingUtils;
粤ICP备19079148号