webgpuBufferManager.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import { WebGPUDataBuffer } from "../../Meshes/WebGPU/webgpuDataBuffer.js";
  2. import { FromHalfFloat } from "../../Misc/textureTools.js";
  3. import { allocateAndCopyTypedBuffer } from "../Extensions/engine.readTexture.js";
  4. // eslint-disable-next-line @typescript-eslint/naming-convention
  5. import * as WebGPUConstants from "./webgpuConstants.js";
  6. /** @internal */
  7. export class WebGPUBufferManager {
  8. static _IsGPUBuffer(buffer) {
  9. return buffer.underlyingResource === undefined;
  10. }
  11. static _FlagsToString(flags, suffix = "") {
  12. let result = suffix;
  13. for (let i = 0; i <= 9; ++i) {
  14. if (flags & (1 << i)) {
  15. if (result) {
  16. result += "_";
  17. }
  18. result += WebGPUConstants.BufferUsage[1 << i];
  19. }
  20. }
  21. return result;
  22. }
  23. constructor(engine, device) {
  24. this._deferredReleaseBuffers = [];
  25. this._engine = engine;
  26. this._device = device;
  27. }
  28. createRawBuffer(viewOrSize, flags, mappedAtCreation = false, label) {
  29. const alignedLength = viewOrSize.byteLength !== undefined ? (viewOrSize.byteLength + 3) & ~3 : (viewOrSize + 3) & ~3; // 4 bytes alignments (because of the upload which requires this)
  30. const verticesBufferDescriptor = {
  31. label: "BabylonWebGPUDevice" + this._engine.uniqueId + "_" + WebGPUBufferManager._FlagsToString(flags, label ?? "Buffer") + "_size" + alignedLength,
  32. mappedAtCreation,
  33. size: alignedLength,
  34. usage: flags,
  35. };
  36. return this._device.createBuffer(verticesBufferDescriptor);
  37. }
  38. createBuffer(viewOrSize, flags, label) {
  39. const isView = viewOrSize.byteLength !== undefined;
  40. const buffer = this.createRawBuffer(viewOrSize, flags, undefined, label);
  41. const dataBuffer = new WebGPUDataBuffer(buffer);
  42. dataBuffer.references = 1;
  43. dataBuffer.capacity = isView ? viewOrSize.byteLength : viewOrSize;
  44. dataBuffer.engineId = this._engine.uniqueId;
  45. if (isView) {
  46. this.setSubData(dataBuffer, 0, viewOrSize);
  47. }
  48. return dataBuffer;
  49. }
  50. setRawData(buffer, dstByteOffset, src, srcByteOffset, byteLength) {
  51. this._device.queue.writeBuffer(buffer, dstByteOffset, src.buffer, srcByteOffset, byteLength);
  52. }
  53. setSubData(dataBuffer, dstByteOffset, src, srcByteOffset = 0, byteLength = 0) {
  54. const buffer = dataBuffer.underlyingResource;
  55. byteLength = byteLength || src.byteLength;
  56. byteLength = Math.min(byteLength, dataBuffer.capacity - dstByteOffset);
  57. // After Migration to Canary
  58. let chunkStart = src.byteOffset + srcByteOffset;
  59. let chunkEnd = chunkStart + byteLength;
  60. // 4 bytes alignments for upload
  61. const alignedLength = (byteLength + 3) & ~3;
  62. if (alignedLength !== byteLength) {
  63. const tempView = new Uint8Array(src.buffer.slice(chunkStart, chunkEnd));
  64. src = new Uint8Array(alignedLength);
  65. src.set(tempView);
  66. srcByteOffset = 0;
  67. chunkStart = 0;
  68. chunkEnd = alignedLength;
  69. byteLength = alignedLength;
  70. }
  71. // Chunk
  72. const maxChunk = 1024 * 1024 * 15;
  73. let offset = 0;
  74. while (chunkEnd - (chunkStart + offset) > maxChunk) {
  75. this._device.queue.writeBuffer(buffer, dstByteOffset + offset, src.buffer, chunkStart + offset, maxChunk);
  76. offset += maxChunk;
  77. }
  78. this._device.queue.writeBuffer(buffer, dstByteOffset + offset, src.buffer, chunkStart + offset, byteLength - offset);
  79. }
  80. _getHalfFloatAsFloatRGBAArrayBuffer(dataLength, arrayBuffer, destArray) {
  81. if (!destArray) {
  82. destArray = new Float32Array(dataLength);
  83. }
  84. const srcData = new Uint16Array(arrayBuffer);
  85. while (dataLength--) {
  86. destArray[dataLength] = FromHalfFloat(srcData[dataLength]);
  87. }
  88. return destArray;
  89. }
  90. readDataFromBuffer(gpuBuffer, size, width, height, bytesPerRow, bytesPerRowAligned, type = 0, offset = 0, buffer = null, destroyBuffer = true, noDataConversion = false) {
  91. const floatFormat = type === 1 ? 2 : type === 2 ? 1 : 0;
  92. const engineId = this._engine.uniqueId;
  93. return new Promise((resolve, reject) => {
  94. gpuBuffer.mapAsync(WebGPUConstants.MapMode.Read, offset, size).then(() => {
  95. const copyArrayBuffer = gpuBuffer.getMappedRange(offset, size);
  96. let data = buffer;
  97. if (noDataConversion) {
  98. if (data === null) {
  99. data = allocateAndCopyTypedBuffer(type, size, true, copyArrayBuffer);
  100. }
  101. else {
  102. data = allocateAndCopyTypedBuffer(type, data.buffer, undefined, copyArrayBuffer);
  103. }
  104. }
  105. else {
  106. if (data === null) {
  107. switch (floatFormat) {
  108. case 0: // byte format
  109. data = new Uint8Array(size);
  110. data.set(new Uint8Array(copyArrayBuffer));
  111. break;
  112. case 1: // half float
  113. // TODO WEBGPU use computer shaders (or render pass) to make the conversion?
  114. data = this._getHalfFloatAsFloatRGBAArrayBuffer(size / 2, copyArrayBuffer);
  115. break;
  116. case 2: // float
  117. data = new Float32Array(size / 4);
  118. data.set(new Float32Array(copyArrayBuffer));
  119. break;
  120. }
  121. }
  122. else {
  123. switch (floatFormat) {
  124. case 0: // byte format
  125. data = new Uint8Array(data.buffer);
  126. data.set(new Uint8Array(copyArrayBuffer));
  127. break;
  128. case 1: // half float
  129. // TODO WEBGPU use computer shaders (or render pass) to make the conversion?
  130. data = this._getHalfFloatAsFloatRGBAArrayBuffer(size / 2, copyArrayBuffer, buffer);
  131. break;
  132. case 2: // float
  133. data = new Float32Array(data.buffer);
  134. data.set(new Float32Array(copyArrayBuffer));
  135. break;
  136. }
  137. }
  138. }
  139. if (bytesPerRow !== bytesPerRowAligned) {
  140. // TODO WEBGPU use computer shaders (or render pass) to build the final buffer data?
  141. if (floatFormat === 1 && !noDataConversion) {
  142. // half float have been converted to float above
  143. bytesPerRow *= 2;
  144. bytesPerRowAligned *= 2;
  145. }
  146. const data2 = new Uint8Array(data.buffer);
  147. let offset = bytesPerRow, offset2 = 0;
  148. for (let y = 1; y < height; ++y) {
  149. offset2 = y * bytesPerRowAligned;
  150. for (let x = 0; x < bytesPerRow; ++x) {
  151. data2[offset++] = data2[offset2++];
  152. }
  153. }
  154. if (floatFormat !== 0 && !noDataConversion) {
  155. data = new Float32Array(data2.buffer, 0, offset / 4);
  156. }
  157. else {
  158. data = new Uint8Array(data2.buffer, 0, offset);
  159. }
  160. }
  161. gpuBuffer.unmap();
  162. if (destroyBuffer) {
  163. this.releaseBuffer(gpuBuffer);
  164. }
  165. resolve(data);
  166. }, (reason) => {
  167. if (this._engine.isDisposed || this._engine.uniqueId !== engineId) {
  168. // The engine was disposed while waiting for the promise, or a context loss/restoration has occurred: don't reject
  169. resolve(new Uint8Array());
  170. }
  171. else {
  172. reject(reason);
  173. }
  174. });
  175. });
  176. }
  177. releaseBuffer(buffer) {
  178. if (WebGPUBufferManager._IsGPUBuffer(buffer)) {
  179. this._deferredReleaseBuffers.push(buffer);
  180. return true;
  181. }
  182. buffer.references--;
  183. if (buffer.references === 0) {
  184. this._deferredReleaseBuffers.push(buffer.underlyingResource);
  185. return true;
  186. }
  187. return false;
  188. }
  189. destroyDeferredBuffers() {
  190. for (let i = 0; i < this._deferredReleaseBuffers.length; ++i) {
  191. this._deferredReleaseBuffers[i].destroy();
  192. }
  193. this._deferredReleaseBuffers.length = 0;
  194. }
  195. }
  196. //# sourceMappingURL=webgpuBufferManager.js.map