webgpuBufferManager.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import { DataBuffer } from '../../Meshes/dataBuffer';
  2. import { WebGPUDataBuffer } from '../../Meshes/WebGPU/webgpuDataBuffer';
  3. import { Nullable } from '../../types';
  4. /** @hidden */
  5. export class WebGPUBufferManager {
  6. private _device: GPUDevice;
  7. private _deferredReleaseBuffers: Array<GPUBuffer> = [];
  8. private static _IsGPUBuffer(buffer: DataBuffer | GPUBuffer): buffer is GPUBuffer {
  9. return (buffer as DataBuffer).underlyingResource === undefined;
  10. }
  11. constructor(device: GPUDevice) {
  12. this._device = device;
  13. }
  14. public createRawBuffer(viewOrSize: ArrayBufferView | number, flags: GPUBufferUsageFlags, mappedAtCreation = false): GPUBuffer {
  15. const alignedLength = (viewOrSize as ArrayBufferView).byteLength !== undefined ? ((viewOrSize as ArrayBufferView).byteLength + 3) & ~3 : ((viewOrSize as number) + 3) & ~3; // 4 bytes alignments (because of the upload which requires this)
  16. const verticesBufferDescriptor = {
  17. mappedAtCreation,
  18. size: alignedLength,
  19. usage: flags
  20. };
  21. return this._device.createBuffer(verticesBufferDescriptor);
  22. }
  23. public createBuffer(viewOrSize: ArrayBufferView | number, flags: GPUBufferUsageFlags): DataBuffer {
  24. const isView = (viewOrSize as ArrayBufferView).byteLength !== undefined;
  25. const buffer = this.createRawBuffer(viewOrSize, flags);
  26. const dataBuffer = new WebGPUDataBuffer(buffer);
  27. dataBuffer.references = 1;
  28. dataBuffer.capacity = isView ? (viewOrSize as ArrayBufferView).byteLength : viewOrSize as number;
  29. if (isView) {
  30. this.setSubData(dataBuffer, 0, viewOrSize as ArrayBufferView);
  31. }
  32. return dataBuffer;
  33. }
  34. public setSubData(dataBuffer: WebGPUDataBuffer, dstByteOffset: number, src: ArrayBufferView, srcByteOffset = 0, byteLength = 0): void {
  35. const buffer = dataBuffer.underlyingResource as GPUBuffer;
  36. byteLength = byteLength || src.byteLength;
  37. byteLength = Math.min(byteLength, dataBuffer.capacity - dstByteOffset);
  38. // After Migration to Canary
  39. let chunkStart = src.byteOffset + srcByteOffset;
  40. let chunkEnd = chunkStart + byteLength;
  41. // 4 bytes alignments for upload
  42. const alignedLength = (byteLength + 3) & ~3;
  43. if (alignedLength !== byteLength) {
  44. const tempView = new Uint8Array(src.buffer.slice(chunkStart, chunkEnd));
  45. src = new Uint8Array(alignedLength);
  46. (src as Uint8Array).set(tempView);
  47. srcByteOffset = 0;
  48. chunkStart = 0;
  49. chunkEnd = alignedLength;
  50. byteLength = alignedLength;
  51. }
  52. // Chunk
  53. const maxChunk = 1024 * 1024 * 15;
  54. let offset = 0;
  55. while ((chunkEnd - (chunkStart + offset)) > maxChunk) {
  56. this._device.defaultQueue.writeBuffer(buffer, dstByteOffset + offset, src.buffer, chunkStart + offset, maxChunk);
  57. offset += maxChunk;
  58. }
  59. this._device.defaultQueue.writeBuffer(buffer, dstByteOffset + offset, src.buffer, chunkStart + offset, byteLength - offset);
  60. }
  61. private _FromHalfFloat(value: number): number {
  62. const s = (value & 0x8000) >> 15;
  63. const e = (value & 0x7C00) >> 10;
  64. const f = value & 0x03FF;
  65. if (e === 0) {
  66. return (s ? -1 : 1) * Math.pow(2, -14) * (f / Math.pow(2, 10));
  67. } else if (e == 0x1F) {
  68. return f ? NaN : ((s ? -1 : 1) * Infinity);
  69. }
  70. return (s ? -1 : 1) * Math.pow(2, e - 15) * (1 + (f / Math.pow(2, 10)));
  71. }
  72. private _GetHalfFloatAsFloatRGBAArrayBuffer(dataLength: number, arrayBuffer: ArrayBuffer, destArray?: Float32Array): Float32Array {
  73. if (!destArray) {
  74. destArray = new Float32Array(dataLength);
  75. }
  76. const srcData = new Uint16Array(arrayBuffer);
  77. while (dataLength--) {
  78. destArray[dataLength] = this._FromHalfFloat(srcData[dataLength]);
  79. }
  80. return destArray;
  81. }
  82. public readDataFromBuffer(gpuBuffer: GPUBuffer, size: number, width: number, height: number, bytesPerRow: number, bytesPerRowAligned: number, floatFormat = 0, offset = 0, buffer: Nullable<ArrayBufferView> = null, destroyBuffer = true): Promise<ArrayBufferView> {
  83. return new Promise((resolve, reject) => {
  84. gpuBuffer.mapAsync(GPUMapMode.READ, offset, size).then(() => {
  85. const copyArrayBuffer = gpuBuffer.getMappedRange(offset, size);
  86. let data: Nullable<ArrayBufferView> | Uint8Array | Float32Array = buffer;
  87. if (data === null) {
  88. switch (floatFormat) {
  89. case 0: // byte format
  90. data = new Uint8Array(size);
  91. (data as Uint8Array).set(new Uint8Array(copyArrayBuffer));
  92. break;
  93. case 1: // half float
  94. // TODO WEBGPU use computer shaders (or render pass) to make the conversion?
  95. data = this._GetHalfFloatAsFloatRGBAArrayBuffer(size / 2, copyArrayBuffer);
  96. break;
  97. case 2: // float
  98. data = new Float32Array(size / 4);
  99. (data as Float32Array).set(new Float32Array(copyArrayBuffer));
  100. break;
  101. }
  102. } else {
  103. switch (floatFormat) {
  104. case 0: // byte format
  105. data = new Uint8Array(data.buffer);
  106. (data as Uint8Array).set(new Uint8Array(copyArrayBuffer));
  107. break;
  108. case 1: // half float
  109. // TODO WEBGPU use computer shaders (or render pass) to make the conversion?
  110. data = this._GetHalfFloatAsFloatRGBAArrayBuffer(size / 2, copyArrayBuffer, buffer as Float32Array);
  111. break;
  112. case 2: // float
  113. data = new Float32Array(data.buffer);
  114. (data as Float32Array).set(new Float32Array(copyArrayBuffer));
  115. break;
  116. }
  117. }
  118. if (bytesPerRow !== bytesPerRowAligned) {
  119. // TODO WEBGPU use computer shaders (or render pass) to build the final buffer data?
  120. if (floatFormat === 1) {
  121. // half float have been converted to float above
  122. bytesPerRow *= 2;
  123. bytesPerRowAligned *= 2;
  124. }
  125. const data2 = new Uint8Array(data!.buffer);
  126. let offset = bytesPerRow, offset2 = 0;
  127. for (let y = 1; y < height; ++y) {
  128. offset2 = y * bytesPerRowAligned;
  129. for (let x = 0; x < bytesPerRow; ++x) {
  130. data2[offset++] = data2[offset2++];
  131. }
  132. }
  133. if (floatFormat !== 0) {
  134. data = new Float32Array(data2.buffer, 0, offset / 4);
  135. } else {
  136. data = new Uint8Array(data2.buffer, 0, offset);
  137. }
  138. }
  139. gpuBuffer.unmap();
  140. if (destroyBuffer) {
  141. this.releaseBuffer(gpuBuffer);
  142. }
  143. resolve(data!);
  144. }, (reason) => reject(reason));
  145. });
  146. }
  147. public releaseBuffer(buffer: DataBuffer | GPUBuffer): boolean {
  148. if (WebGPUBufferManager._IsGPUBuffer(buffer)) {
  149. this._deferredReleaseBuffers.push(buffer);
  150. return true;
  151. }
  152. buffer.references--;
  153. if (buffer.references === 0) {
  154. this._deferredReleaseBuffers.push(buffer.underlyingResource as GPUBuffer);
  155. return true;
  156. }
  157. return false;
  158. }
  159. public destroyDeferredBuffers(): void {
  160. for (let i = 0; i < this._deferredReleaseBuffers.length; ++i) {
  161. this._deferredReleaseBuffers[i].destroy();
  162. }
  163. this._deferredReleaseBuffers.length = 0;
  164. }
  165. }