// 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 = (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; }; /** Abstract GPU buffer */ export abstract class Buffer extends Resource { /** 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>, byteOffset?: number, byteLength?: number ): Promise; /** Reads data asynchronously, returns a copy of the buffer data */ abstract readAsync(byteOffset?: number, byteLength?: number): Promise; /** Maps buffer data to CPU memory. Mapped memory is only accessible in the callback */ abstract mapAndReadAsync( onMapped: BufferMapCallback, byteOffset?: number, byteLength?: number ): Promise; /** 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 = { ...Resource.defaultProps, usage: 0, // Buffer.COPY_DST | Buffer.COPY_SRC byteLength: 0, byteOffset: 0, data: null, indexType: 'uint16', onMapped: undefined! }; }