WebGPUAttributeUtils.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. import { Float16BufferAttribute } from 'three';
  2. import { GPUInputStepMode } from './WebGPUConstants.js';
  3. const typedArraysToVertexFormatPrefix = new Map( [
  4. [ Int8Array, [ 'sint8', 'snorm8' ]],
  5. [ Uint8Array, [ 'uint8', 'unorm8' ]],
  6. [ Int16Array, [ 'sint16', 'snorm16' ]],
  7. [ Uint16Array, [ 'uint16', 'unorm16' ]],
  8. [ Int32Array, [ 'sint32', 'snorm32' ]],
  9. [ Uint32Array, [ 'uint32', 'unorm32' ]],
  10. [ Float32Array, [ 'float32', ]],
  11. ] );
  12. const typedAttributeToVertexFormatPrefix = new Map( [
  13. [ Float16BufferAttribute, [ 'float16', ]],
  14. ] );
  15. const typeArraysToVertexFormatPrefixForItemSize1 = new Map( [
  16. [ Int32Array, 'sint32' ],
  17. [ Int16Array, 'sint32' ], // patch for INT16
  18. [ Uint32Array, 'uint32' ],
  19. [ Uint16Array, 'uint32' ], // patch for UINT16
  20. [ Float32Array, 'float32' ]
  21. ] );
  22. class WebGPUAttributeUtils {
  23. constructor( backend ) {
  24. this.backend = backend;
  25. }
  26. createAttribute( attribute, usage ) {
  27. const bufferAttribute = this._getBufferAttribute( attribute );
  28. const backend = this.backend;
  29. const bufferData = backend.get( bufferAttribute );
  30. let buffer = bufferData.buffer;
  31. if ( buffer === undefined ) {
  32. const device = backend.device;
  33. let array = bufferAttribute.array;
  34. // patch for INT16 and UINT16
  35. if ( attribute.normalized === false && ( array.constructor === Int16Array || array.constructor === Uint16Array ) ) {
  36. const tempArray = new Uint32Array( array.length );
  37. for ( let i = 0; i < array.length; i ++ ) {
  38. tempArray[ i ] = array[ i ];
  39. }
  40. array = tempArray;
  41. }
  42. bufferAttribute.array = array;
  43. if ( ( bufferAttribute.isStorageBufferAttribute || bufferAttribute.isStorageInstancedBufferAttribute ) && bufferAttribute.itemSize === 3 ) {
  44. array = new array.constructor( bufferAttribute.count * 4 );
  45. for ( let i = 0; i < bufferAttribute.count; i ++ ) {
  46. array.set( bufferAttribute.array.subarray( i * 3, i * 3 + 3 ), i * 4 );
  47. }
  48. // Update BufferAttribute
  49. bufferAttribute.itemSize = 4;
  50. bufferAttribute.array = array;
  51. }
  52. const size = array.byteLength + ( ( 4 - ( array.byteLength % 4 ) ) % 4 ); // ensure 4 byte alignment, see #20441
  53. buffer = device.createBuffer( {
  54. label: bufferAttribute.name,
  55. size: size,
  56. usage: usage,
  57. mappedAtCreation: true
  58. } );
  59. new array.constructor( buffer.getMappedRange() ).set( array );
  60. buffer.unmap();
  61. bufferData.buffer = buffer;
  62. }
  63. }
  64. updateAttribute( attribute ) {
  65. const bufferAttribute = this._getBufferAttribute( attribute );
  66. const backend = this.backend;
  67. const device = backend.device;
  68. const buffer = backend.get( bufferAttribute ).buffer;
  69. const array = bufferAttribute.array;
  70. const updateRanges = bufferAttribute.updateRanges;
  71. if ( updateRanges.length === 0 ) {
  72. // Not using update ranges
  73. device.queue.writeBuffer(
  74. buffer,
  75. 0,
  76. array,
  77. 0
  78. );
  79. } else {
  80. for ( let i = 0, l = updateRanges.length; i < l; i ++ ) {
  81. const range = updateRanges[ i ];
  82. device.queue.writeBuffer(
  83. buffer,
  84. 0,
  85. array,
  86. range.start * array.BYTES_PER_ELEMENT,
  87. range.count * array.BYTES_PER_ELEMENT
  88. );
  89. }
  90. bufferAttribute.clearUpdateRanges();
  91. }
  92. }
  93. createShaderVertexBuffers( renderObject ) {
  94. const attributes = renderObject.getAttributes();
  95. const vertexBuffers = new Map();
  96. for ( let slot = 0; slot < attributes.length; slot ++ ) {
  97. const geometryAttribute = attributes[ slot ];
  98. const bytesPerElement = geometryAttribute.array.BYTES_PER_ELEMENT;
  99. const bufferAttribute = this._getBufferAttribute( geometryAttribute );
  100. let vertexBufferLayout = vertexBuffers.get( bufferAttribute );
  101. if ( vertexBufferLayout === undefined ) {
  102. let arrayStride, stepMode;
  103. if ( geometryAttribute.isInterleavedBufferAttribute === true ) {
  104. arrayStride = geometryAttribute.data.stride * bytesPerElement;
  105. stepMode = geometryAttribute.data.isInstancedInterleavedBuffer ? GPUInputStepMode.Instance : GPUInputStepMode.Vertex;
  106. } else {
  107. arrayStride = geometryAttribute.itemSize * bytesPerElement;
  108. stepMode = geometryAttribute.isInstancedBufferAttribute ? GPUInputStepMode.Instance : GPUInputStepMode.Vertex;
  109. }
  110. // patch for INT16 and UINT16
  111. if ( geometryAttribute.normalized === false && ( geometryAttribute.array.constructor === Int16Array || geometryAttribute.array.constructor === Uint16Array ) ) {
  112. arrayStride = 4;
  113. }
  114. vertexBufferLayout = {
  115. arrayStride,
  116. attributes: [],
  117. stepMode
  118. };
  119. vertexBuffers.set( bufferAttribute, vertexBufferLayout );
  120. }
  121. const format = this._getVertexFormat( geometryAttribute );
  122. const offset = ( geometryAttribute.isInterleavedBufferAttribute === true ) ? geometryAttribute.offset * bytesPerElement : 0;
  123. vertexBufferLayout.attributes.push( {
  124. shaderLocation: slot,
  125. offset,
  126. format
  127. } );
  128. }
  129. return Array.from( vertexBuffers.values() );
  130. }
  131. destroyAttribute( attribute ) {
  132. const backend = this.backend;
  133. const data = backend.get( this._getBufferAttribute( attribute ) );
  134. data.buffer.destroy();
  135. backend.delete( attribute );
  136. }
  137. async getArrayBufferAsync( attribute ) {
  138. const backend = this.backend;
  139. const device = backend.device;
  140. const data = backend.get( this._getBufferAttribute( attribute ) );
  141. const bufferGPU = data.buffer;
  142. const size = bufferGPU.size;
  143. let readBufferGPU = data.readBuffer;
  144. let needsUnmap = true;
  145. if ( readBufferGPU === undefined ) {
  146. readBufferGPU = device.createBuffer( {
  147. label: attribute.name,
  148. size,
  149. usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
  150. } );
  151. needsUnmap = false;
  152. data.readBuffer = readBufferGPU;
  153. }
  154. const cmdEncoder = device.createCommandEncoder( {} );
  155. cmdEncoder.copyBufferToBuffer(
  156. bufferGPU,
  157. 0,
  158. readBufferGPU,
  159. 0,
  160. size
  161. );
  162. if ( needsUnmap ) readBufferGPU.unmap();
  163. const gpuCommands = cmdEncoder.finish();
  164. device.queue.submit( [ gpuCommands ] );
  165. await readBufferGPU.mapAsync( GPUMapMode.READ );
  166. const arrayBuffer = readBufferGPU.getMappedRange();
  167. return arrayBuffer;
  168. }
  169. _getVertexFormat( geometryAttribute ) {
  170. const { itemSize, normalized } = geometryAttribute;
  171. const ArrayType = geometryAttribute.array.constructor;
  172. const AttributeType = geometryAttribute.constructor;
  173. let format;
  174. if ( itemSize == 1 ) {
  175. format = typeArraysToVertexFormatPrefixForItemSize1.get( ArrayType );
  176. } else {
  177. const prefixOptions = typedAttributeToVertexFormatPrefix.get( AttributeType ) || typedArraysToVertexFormatPrefix.get( ArrayType );
  178. const prefix = prefixOptions[ normalized ? 1 : 0 ];
  179. if ( prefix ) {
  180. const bytesPerUnit = ArrayType.BYTES_PER_ELEMENT * itemSize;
  181. const paddedBytesPerUnit = Math.floor( ( bytesPerUnit + 3 ) / 4 ) * 4;
  182. const paddedItemSize = paddedBytesPerUnit / ArrayType.BYTES_PER_ELEMENT;
  183. if ( paddedItemSize % 1 ) {
  184. throw new Error( 'THREE.WebGPUAttributeUtils: Bad vertex format item size.' );
  185. }
  186. format = `${prefix}x${paddedItemSize}`;
  187. }
  188. }
  189. if ( ! format ) {
  190. console.error( 'THREE.WebGPUAttributeUtils: Vertex format not supported yet.' );
  191. }
  192. return format;
  193. }
  194. _getBufferAttribute( attribute ) {
  195. if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
  196. return attribute;
  197. }
  198. }
  199. export default WebGPUAttributeUtils;