UNPKG

@absulit/points

Version:

A Generative Art library made in WebGPU

346 lines (293 loc) 8.66 kB
import { getWGSLType } from './data-size.js' /** * Storage is a container for storage buffer related data and actions. * @class Storage */ class Storage { #name #mapped #type #shaderStage = GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE #readable = false #buffer = null #bufferRead = null #internal = false #stream = false #updated = false #value #size = null // TODO: document this: to force allocate more space in case an update is greater than the default array size /** * @param {{name:String, value:(Number|Array<Number>), type:String, readable:Boolean, shaderStage:GPUShaderStage, stream:bool, updated:bool, size:Number}} config */ constructor({ name, value, type, readable, shaderStage, stream = false, updated = false, size = null }) { this.#validateName(name); this.#validateType(type); this.#validateValue(value); this.#name = name; this.#mapped = !!value; this.#type = type || getWGSLType(value); this.#readable = readable || this.#readable; this.#shaderStage = shaderStage || this.#shaderStage; this.#value = value; this.#stream = stream; this.#updated = updated; this.#size = size; Object.seal(this); } #ifTypeVecGetVecValue(type, value) { let newValue = value; if (type.startsWith('vec')) { newValue = `vec${value.length}f(${value})` } return newValue; } get name() { return this.#name; } /** * The name that the Storage will have on the WGSL side. * @param {String} value name of the Storage. The name is used in the WGSL * shader. * @example * // js * myStorage.name = 'myStorageName'; * * // wgsl * myStorageName = 13.1; * @memberof Storage */ set name(value) { this.#validateName(value); this.#name = value; } get mapped() { return this.#mapped; } /** * @param {Boolean} value tells WebGPU if the Storage is mapped or not. This * allows for the initialization of the Storage with data, which is a * different route. * @memberof Storage */ set mapped(value) { this.#mapped = value; } get type() { return this.#type; } /** * @param {String} value WGSL data type of the Storage. * @example * myStorage.type = 'u32' * @memberof Storage */ set type(value) { this.#validateType(value); this.#type = value || getArrayType(this.#value) || 'f32'; } get shaderStage() { return this.#shaderStage; } /** * Tells WebGPU to which shader it can only be used. * @param {GPUShaderStage} value * @memberof Storage */ set shaderStage(value) { this.#shaderStage = value; } get readable() { return this.#readable; } /** * If data is read back in JS from WGSL, then set to `true`. * @param {Boolean} value * @memberof Storage */ set readable(value) { this.#readable = value; } get buffer() { return this.#buffer; } /** * For internal use mostly. The actual {@link GPUBuffer} with the data. * @memberof Storage */ set buffer(value) { this.#buffer = value; } get bufferRead() { return this.#bufferRead; } /** * Buffer for reading back * For internal use mostly. The actual GPUBufferRead with the data. * @memberof Storage */ set bufferRead(value) { this.#bufferRead = value; } get internal() { return this.#internal; } set internal(value) { this.#internal = value; } get size() { return this.#size; } set size(value) { this.#size = value; } get stream() { return this.#stream; } /** * `updated` is set to true in data updates, but this is not true in * something like audio, where the data streams and needs to be updated * constantly, so if the storage map needs to be updated constantly then * `stream` needs to be set to true. * @param {boolean} value * @memberof Storage */ set stream(value) { this.#stream = value; } get updated() { return this.#updated; } /** * Mostly internal. Set to `true` if a value has been updated. * @memberof Storage */ set updated(value) { this.#updated = value; } get value() { let value = this.#value; // Internally, what Points use to create the buffer is a Uint8Array // TODO: maybe move to POINTS? if (value && !Array.isArray(value) && value.constructor !== Uint8Array) { value = new Uint8Array([value]); } return value; } /** * @param {Number|Array<Number>} value data to send to the shader * @memberof Storage */ set value(value) { this.#validateValue(value); this.#mapped = !!value; const type = this.#type || getWGSLType(value); this.#value = value; this.#type = type; this.#updated = true; } /** * * @param {Number|Array<Number>} value data to send to the shader * @returns {Storage} * @memberof Storage */ setValue(value) { this.#validateValue(value); this.#mapped = true; this.#updated = true; const type = this.#type || getWGSLType(value); this.#value = value; this.#type = type; return this; } /** * if this is going to be used to read data back set to `true` * @param {bool} value * @returns {Storage} * @memberof Storage */ setReadable(value) { this.#readable = value; return this; } /** * Tells WebGPU to which shader it can only be used. * @param {GPUShaderStage} value * @returns {Storage} * @memberof Storage */ setShaderStage(value) { this.#shaderStage = value; return this; } /** * @param {String} value WGSL data type of the Storage. * @returns {Storage} * @example * myStorage.setType('u32'); * @memberof Storage */ setType(value) { this.#validateType(value); this.#type = value || getArrayType(value) || 'f32'; return this; } async read() { let arrayBufferCopy = null; if (this.#readable) { try { await this.#bufferRead.mapAsync(GPUMapMode.READ); const arrayBuffer = this.#bufferRead.getMappedRange(); arrayBufferCopy = new Float32Array(arrayBuffer.slice(0)); this.#bufferRead.unmap(); this.#value = arrayBufferCopy; } catch (error) { // if we switch projects mapasync fails // we ignore it } } return arrayBufferCopy; } #validateValue(value) { if (value && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Uint8Array)) { throw `Storage '${this.#name}' value:'${value}' can't be an Object.` } if (typeof value === 'string') { throw `Storage '${this.#name}' value: '${value}' can't be an String.` } const isArray = Array.isArray(value); if (isArray) { const { length } = value; if (length < 2) { throw `Constant named '${this.#name}': Size of the array is lower than 2. There's no vec1`; } if (Array.isArray(this.#value)) { if (length != this.#value.length) { throw `Storage named '${this.#name}': Size of the array value has changed from ${this.#value.length} to ${length}.` } } } } #validateName(value) { if (typeof value === 'number') { throw `Storage name '${this.#name}' can't be an Number.` } if (typeof value === 'string') { const valNumber = +value; if (!Number.isNaN(valNumber) && typeof valNumber === 'number') { throw `Storage name '${this.#name}' can't be an Number.` } } } #validateType(value) { } // allows for things like: // storage.myStorage += 10 // works on set, not on get // on get you obtain the Storage valueOf() { return this.#value; } } export default Storage;