@luma.gl/core
Version:
The luma.gl core Device API
165 lines (140 loc) • 5.68 kB
text/typescript
// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import type {Device} from '../device';
import {Resource, ResourceProps} from './resource';
/** Callback for Buffer.mapAndReadAsync */
export type BufferMapCallback<T> = (arrayBuffer: ArrayBuffer, lifetime: 'mapped' | 'copied') => T;
export type BufferProps = ResourceProps & {
/** Supply a handle to connect to an existing device-specific buffer */
handle?: WebGLBuffer;
/** Specifies how this buffer can be used */
usage?: number;
/** Length in bytes of memory to be allocated. If not specified, `byteLength` of `props.data` will be used. */
byteLength?: number;
/** Byte offset into the newly created Buffer to store data at */
byteOffset?: number;
/** If props.usage includes Buffer.INDEX. Note: uint8 indices are automatically converted to uint16 for WebGPU compatibility */
indexType?: 'uint8' | 'uint16' | 'uint32';
/** Data to initialize the buffer with. */
data?: ArrayBuffer | ArrayBufferView | null;
/** Callback to initialize data without copy */
onMapped?: BufferMapCallback<void>;
};
/** Abstract GPU buffer */
export abstract class Buffer extends Resource<BufferProps> {
/** Index buffer */
static INDEX = 0x0010;
/** Vertex buffer */
static VERTEX = 0x0020;
/** Uniform buffer */
static UNIFORM = 0x0040;
/** Storage buffer */
static STORAGE = 0x0080;
static INDIRECT = 0x0100;
static QUERY_RESOLVE = 0x0200;
// Usage Flags
static MAP_READ = 0x01;
static MAP_WRITE = 0x02;
static COPY_SRC = 0x0004;
static COPY_DST = 0x0008;
override get [Symbol.toStringTag](): string {
return 'Buffer';
}
/** The usage with which this buffer was created */
readonly usage: number;
/** For index buffers, whether indices are 8, 16 or 32 bit. Note: uint8 indices are automatically converted to uint16 for WebGPU compatibility */
readonly indexType?: 'uint8' | 'uint16' | 'uint32';
/** Length of buffer in bytes */
abstract byteLength: number;
/** "Time" of last update, can be used to check if redraw is needed */
updateTimestamp: number;
constructor(device: Device, props: BufferProps) {
const deducedProps = {...props};
// Deduce indexType
if ((props.usage || 0) & Buffer.INDEX && !props.indexType) {
if (props.data instanceof Uint32Array) {
deducedProps.indexType = 'uint32';
} else if (props.data instanceof Uint16Array) {
deducedProps.indexType = 'uint16';
} else if (props.data instanceof Uint8Array) {
deducedProps.indexType = 'uint8';
}
}
// Remove data from props before storing, we don't want to hold on to a big chunk of memory
delete deducedProps.data;
super(device, deducedProps, Buffer.defaultProps);
this.usage = deducedProps.usage || 0;
this.indexType = deducedProps.indexType;
// TODO - perhaps this should be set on async write completion?
this.updateTimestamp = device.incrementTimestamp();
}
/**
* Create a copy of this Buffer with new byteLength, with same props but of the specified size.
* @note Does not copy contents of the cloned Buffer.
*/
clone(props: {byteLength: number}): Buffer {
return this.device.createBuffer({...this.props, ...props});
}
/** Write data to buffer */
abstract write(
data: ArrayBufferLike | ArrayBufferView | SharedArrayBuffer,
byteOffset?: number
): void;
abstract mapAndWriteAsync(
onMapped: BufferMapCallback<void | Promise<void>>,
byteOffset?: number,
byteLength?: number
): Promise<void>;
/** Reads data asynchronously, returns a copy of the buffer data */
abstract readAsync(byteOffset?: number, byteLength?: number): Promise<Uint8Array>;
/** Maps buffer data to CPU memory. Mapped memory is only accessible in the callback */
abstract mapAndReadAsync<T>(
onMapped: BufferMapCallback<T>,
byteOffset?: number,
byteLength?: number
): Promise<T>;
/** Read data synchronously. @note WebGL2 only */
abstract readSyncWebGL(byteOffset?: number, byteLength?: number): Uint8Array;
// PROTECTED METHODS (INTENDED FOR USE BY OTHER FRAMEWORK CODE ONLY)
/** Max amount of debug data saved. Two vec4's */
static DEBUG_DATA_MAX_LENGTH = 32;
/** A partial CPU-side copy of the data in this buffer, for debugging purposes */
debugData: ArrayBuffer = new ArrayBuffer(0);
/** This doesn't handle partial non-zero offset updates correctly */
protected _setDebugData(
data: ArrayBufferView | ArrayBufferLike | null,
_byteOffset: number,
byteLength: number
): void {
let arrayBufferView: ArrayBufferView | null = null;
let arrayBuffer: ArrayBufferLike | null;
if (ArrayBuffer.isView(data)) {
arrayBufferView = data;
arrayBuffer = data.buffer;
} else {
arrayBuffer = data;
}
const debugDataLength = Math.min(
data ? data.byteLength : byteLength,
Buffer.DEBUG_DATA_MAX_LENGTH
);
if (arrayBuffer === null) {
this.debugData = new ArrayBuffer(debugDataLength);
} else {
const sourceByteOffset = Math.min(arrayBufferView?.byteOffset || 0, arrayBuffer.byteLength);
const availableByteLength = Math.max(0, arrayBuffer.byteLength - sourceByteOffset);
const copyByteLength = Math.min(debugDataLength, availableByteLength);
this.debugData = new Uint8Array(arrayBuffer, sourceByteOffset, copyByteLength).slice().buffer;
}
}
static override defaultProps: Required<BufferProps> = {
...Resource.defaultProps,
usage: 0, // Buffer.COPY_DST | Buffer.COPY_SRC
byteLength: 0,
byteOffset: 0,
data: null,
indexType: 'uint16',
onMapped: undefined!
};
}