webgpuBufferManager.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  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(width: number, height: number, dataOffset: number, dataLength: number, arrayBuffer: ArrayBuffer, destArray?: Float32Array): Float32Array {
  73. if (!destArray) {
  74. destArray = new Float32Array(dataLength);
  75. }
  76. const srcData = new Uint16Array(arrayBuffer, dataOffset);
  77. let index = 0;
  78. for (let y = 0; y < height; y++) {
  79. for (let x = 0; x < width; x++) {
  80. const srcPos = (x + y * width) * 4;
  81. destArray[index] = this._FromHalfFloat(srcData[srcPos]);
  82. destArray[index + 1] = this._FromHalfFloat(srcData[srcPos + 1]);
  83. destArray[index + 2] = this._FromHalfFloat(srcData[srcPos + 2]);
  84. destArray[index + 3] = this._FromHalfFloat(srcData[srcPos + 3]);
  85. index += 4;
  86. }
  87. }
  88. return destArray;
  89. }
  90. public readDataFromBuffer(gpuBuffer: GPUBuffer, size: number, width: number, height: number, bytesPerRow: number, bytesPerRowAlignedAligned: number, floatFormat = 0, offset = 0, buffer: Nullable<ArrayBufferView> = null, destroyBuffer = true): Promise<ArrayBufferView> {
  91. return new Promise((resolve, reject) => {
  92. gpuBuffer.mapAsync(GPUMapMode.READ, offset, size).then(() => {
  93. const copyArrayBuffer = gpuBuffer.getMappedRange(offset, size);
  94. let data: Nullable<ArrayBufferView> | Uint8Array | Float32Array = buffer;
  95. if (data === null) {
  96. switch (floatFormat) {
  97. case 0: // byte format
  98. data = new Uint8Array(size);
  99. (data as Uint8Array).set(new Uint8Array(copyArrayBuffer));
  100. break;
  101. case 1: // half float
  102. // TODO WEBGPU use computer shaders (or render pass) to make the conversion?
  103. data = this._GetHalfFloatAsFloatRGBAArrayBuffer(width, height, 0, size / 2, copyArrayBuffer);
  104. break;
  105. case 2: // float
  106. data = new Float32Array(size / 4);
  107. (data as Float32Array).set(new Float32Array(copyArrayBuffer));
  108. break;
  109. }
  110. } else {
  111. switch (floatFormat) {
  112. case 0: // byte format
  113. data = new Uint8Array(data.buffer);
  114. (data as Uint8Array).set(new Uint8Array(copyArrayBuffer));
  115. break;
  116. case 1: // half float
  117. // TODO WEBGPU use computer shaders (or render pass) to make the conversion?
  118. data = this._GetHalfFloatAsFloatRGBAArrayBuffer(width, height, 0, size / 2, copyArrayBuffer, buffer as Float32Array);
  119. break;
  120. case 2: // float
  121. data = new Float32Array(data.buffer);
  122. (data as Float32Array).set(new Float32Array(copyArrayBuffer));
  123. break;
  124. }
  125. }
  126. if (bytesPerRow !== bytesPerRowAlignedAligned) {
  127. // TODO WEBGPU use computer shaders (or render pass) to build the final buffer data?
  128. const data2: Uint8Array = data as Uint8Array;
  129. let offset = bytesPerRow, offset2 = 0;
  130. for (let y = 1; y < height; ++y) {
  131. offset2 = y * bytesPerRowAlignedAligned;
  132. for (let x = 0; x < bytesPerRow; ++x) {
  133. data2[offset++] = data2[offset2++];
  134. }
  135. }
  136. if (floatFormat !== 0) {
  137. data = new Float32Array(data2.buffer, 0, offset / 4);
  138. } else {
  139. data = new Uint8Array(data2.buffer, 0, offset);
  140. }
  141. }
  142. gpuBuffer.unmap();
  143. if (destroyBuffer) {
  144. this.releaseBuffer(gpuBuffer);
  145. }
  146. resolve(data!);
  147. }, (reason) => reject(reason));
  148. });
  149. }
  150. public releaseBuffer(buffer: DataBuffer | GPUBuffer): boolean {
  151. if (WebGPUBufferManager._IsGPUBuffer(buffer)) {
  152. this._deferredReleaseBuffers.push(buffer);
  153. return true;
  154. }
  155. buffer.references--;
  156. if (buffer.references === 0) {
  157. this._deferredReleaseBuffers.push(buffer.underlyingResource as GPUBuffer);
  158. return true;
  159. }
  160. return false;
  161. }
  162. public destroyDeferredBuffers(): void {
  163. for (let i = 0; i < this._deferredReleaseBuffers.length; ++i) {
  164. this._deferredReleaseBuffers[i].destroy();
  165. }
  166. this._deferredReleaseBuffers.length = 0;
  167. }
  168. }