@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
199 lines (198 loc) • 9.8 kB
JavaScript
import { WebGPUDataBuffer } from "../../Meshes/WebGPU/webgpuDataBuffer.js";
import { FromHalfFloat } from "../../Misc/textureTools.js";
import { allocateAndCopyTypedBuffer } from "../abstractEngine.functions.js";
// eslint-disable-next-line @typescript-eslint/naming-convention
import * as WebGPUConstants from "./webgpuConstants.js";
/** @internal */
export class WebGPUBufferManager {
static _IsGPUBuffer(buffer) {
return buffer.underlyingResource === undefined;
}
static _FlagsToString(flags, suffix = "") {
let result = suffix;
for (let i = 0; i <= 9; ++i) {
if (flags & (1 << i)) {
if (result) {
result += "_";
}
result += WebGPUConstants.BufferUsage[1 << i];
}
}
return result;
}
constructor(engine, device) {
this._deferredReleaseBuffers = [];
this._engine = engine;
this._device = device;
}
createRawBuffer(viewOrSize, flags, mappedAtCreation = false, label) {
const alignedLength = viewOrSize.byteLength !== undefined ? (viewOrSize.byteLength + 3) & ~3 : (viewOrSize + 3) & ~3; // 4 bytes alignments (because of the upload which requires this)
const verticesBufferDescriptor = {
label: "BabylonWebGPUDevice" + this._engine.uniqueId + "_" + WebGPUBufferManager._FlagsToString(flags, label ?? "Buffer") + "_size" + alignedLength,
mappedAtCreation,
size: alignedLength,
usage: flags,
};
return this._device.createBuffer(verticesBufferDescriptor);
}
createBuffer(viewOrSize, flags, label) {
const isView = viewOrSize.byteLength !== undefined;
const dataBuffer = new WebGPUDataBuffer();
const labelId = "DataBufferUniqueId=" + dataBuffer.uniqueId;
dataBuffer.buffer = this.createRawBuffer(viewOrSize, flags, undefined, label ? labelId + "-" + label : labelId);
dataBuffer.references = 1;
dataBuffer.capacity = isView ? viewOrSize.byteLength : viewOrSize;
dataBuffer.engineId = this._engine.uniqueId;
if (isView) {
this.setSubData(dataBuffer, 0, viewOrSize);
}
return dataBuffer;
}
// This calls GPUBuffer.writeBuffer() with no alignment corrections
// dstByteOffset and byteLength must both be aligned to 4 bytes and bytes moved must be within src and dst arrays
setRawData(buffer, dstByteOffset, src, srcByteOffset, byteLength) {
srcByteOffset += src.byteOffset;
this._device.queue.writeBuffer(buffer, dstByteOffset, src.buffer, srcByteOffset, byteLength);
}
// This calls GPUBuffer.writeBuffer() with alignment corrections (dstByteOffset and byteLength will be aligned to 4 byte boundaries)
// If alignment is needed, src must be a full copy of dataBuffer, or at least should be large enough to cope with the additional bytes copied because of alignment!
setSubData(dataBuffer, dstByteOffset, src, srcByteOffset = 0, byteLength = 0) {
const buffer = dataBuffer.underlyingResource;
byteLength = byteLength || src.byteLength - srcByteOffset;
// Make sure the dst offset is aligned to 4 bytes
const startPre = dstByteOffset & 3;
srcByteOffset -= startPre;
dstByteOffset -= startPre;
// Make sure the byte length is aligned to 4 bytes
const originalByteLength = byteLength;
byteLength = (byteLength + startPre + 3) & ~3;
// Check if the backing buffer of src is large enough to cope with the additional bytes copied because of alignment
const backingBufferSize = src.buffer.byteLength - src.byteOffset;
if (backingBufferSize < byteLength) {
// Not enough place in the backing buffer for the aligned copy.
// Creates a new buffer and copy the source data to it.
// The buffer will have byteLength - originalByteLength zeros at the end.
const tmpBuffer = new Uint8Array(byteLength);
tmpBuffer.set(new Uint8Array(src.buffer, src.byteOffset + srcByteOffset, originalByteLength));
src = tmpBuffer;
srcByteOffset = 0;
}
this.setRawData(buffer, dstByteOffset, src, srcByteOffset, byteLength);
}
_getHalfFloatAsFloatRGBAArrayBuffer(dataLength, arrayBuffer, destArray) {
if (!destArray) {
destArray = new Float32Array(dataLength);
}
const srcData = new Uint16Array(arrayBuffer);
while (dataLength--) {
destArray[dataLength] = FromHalfFloat(srcData[dataLength]);
}
return destArray;
}
readDataFromBuffer(gpuBuffer, size, width, height, bytesPerRow, bytesPerRowAligned, type = 0, offset = 0, buffer = null, destroyBuffer = true, noDataConversion = false) {
const floatFormat = type === 1 ? 2 : type === 2 ? 1 : 0;
const engineId = this._engine.uniqueId;
return new Promise((resolve, reject) => {
gpuBuffer.mapAsync(1 /* WebGPUConstants.MapMode.Read */, offset, size).then(() => {
const copyArrayBuffer = gpuBuffer.getMappedRange(offset, size);
let data = buffer;
if (noDataConversion) {
if (data === null) {
data = allocateAndCopyTypedBuffer(type, size, true, copyArrayBuffer);
}
else {
data = allocateAndCopyTypedBuffer(type, data.buffer, undefined, copyArrayBuffer);
}
}
else {
if (data === null) {
switch (floatFormat) {
case 0: // byte format
data = new Uint8Array(size);
data.set(new Uint8Array(copyArrayBuffer));
break;
case 1: // half float
// TODO WEBGPU use computer shaders (or render pass) to make the conversion?
data = this._getHalfFloatAsFloatRGBAArrayBuffer(size / 2, copyArrayBuffer);
break;
case 2: // float
data = new Float32Array(size / 4);
data.set(new Float32Array(copyArrayBuffer));
break;
}
}
else {
switch (floatFormat) {
case 0: // byte format
data = new Uint8Array(data.buffer);
data.set(new Uint8Array(copyArrayBuffer));
break;
case 1: // half float
// TODO WEBGPU use computer shaders (or render pass) to make the conversion?
data = this._getHalfFloatAsFloatRGBAArrayBuffer(size / 2, copyArrayBuffer, buffer);
break;
case 2: // float
data = new Float32Array(data.buffer);
data.set(new Float32Array(copyArrayBuffer));
break;
}
}
}
if (bytesPerRow !== bytesPerRowAligned) {
// TODO WEBGPU use computer shaders (or render pass) to build the final buffer data?
if (floatFormat === 1 && !noDataConversion) {
// half float have been converted to float above
bytesPerRow *= 2;
bytesPerRowAligned *= 2;
}
const data2 = new Uint8Array(data.buffer);
let offset = bytesPerRow, offset2 = 0;
for (let y = 1; y < height; ++y) {
offset2 = y * bytesPerRowAligned;
for (let x = 0; x < bytesPerRow; ++x) {
data2[offset++] = data2[offset2++];
}
}
if (floatFormat !== 0 && !noDataConversion) {
data = new Float32Array(data2.buffer, 0, offset / 4);
}
else {
data = new Uint8Array(data2.buffer, 0, offset);
}
}
gpuBuffer.unmap();
if (destroyBuffer) {
this.releaseBuffer(gpuBuffer);
}
resolve(data);
}, (reason) => {
if (this._engine.isDisposed || this._engine.uniqueId !== engineId) {
// The engine was disposed while waiting for the promise, or a context loss/restoration has occurred: don't reject
resolve(new Uint8Array());
}
else {
reject(reason);
}
});
});
}
releaseBuffer(buffer) {
if (WebGPUBufferManager._IsGPUBuffer(buffer)) {
this._deferredReleaseBuffers.push(buffer);
return true;
}
buffer.references--;
if (buffer.references === 0) {
this._deferredReleaseBuffers.push(buffer.underlyingResource);
return true;
}
return false;
}
destroyDeferredBuffers() {
for (let i = 0; i < this._deferredReleaseBuffers.length; ++i) {
this._deferredReleaseBuffers[i].destroy();
}
this._deferredReleaseBuffers.length = 0;
}
}
//# sourceMappingURL=webgpuBufferManager.js.map