\r\n * slot 0 slot 1 slot 2 slot 3\r\n * row 0 | _ _ _ _ | _ _ _ _ | _ _ _ _ | _ _ _ _ |\r\n * row 1 | _ _ _ _ | _ _ _ _ | _ _ _ _ | _ _ _ _ |\r\n * row 2 | _ _ _ _ | _ _ _ _ | _ _ _ _ | _ _ _ _ |\r\n *\r\n * see https://webgpufundamentals.org/webgpu/lessons/resources/wgsl-offset-computer.html\r\n */\r\nexport class BufferElement {\r\n /** The name of the {@link BufferElement} */\r\n name: string\r\n /** The WGSL variable type of the {@link BufferElement} */\r\n type: WGSLVariableType\r\n /** The key of the {@link BufferElement} */\r\n key: string\r\n\r\n /** {@link BufferLayout} used to fill the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array} at the right offsets */\r\n bufferLayout: BufferLayout\r\n\r\n /**\r\n * Object defining exactly at which place a binding should be inserted into the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n */\r\n alignment: BufferElementAlignment\r\n\r\n /** Array containing the {@link BufferElement} values */\r\n view?: TypedArray\r\n\r\n /**\r\n * BufferElement constructor\r\n * @param parameters - {@link BufferElementParams | parameters} used to create our {@link BufferElement}\r\n */\r\n constructor({ name, key, type = 'f32' }: BufferElementParams) {\r\n this.name = name\r\n this.key = key\r\n this.type = type\r\n\r\n this.bufferLayout = getBufferLayout(this.type.replace('array', '').replace('<', '').replace('>', ''))\r\n\r\n // set init alignment\r\n this.alignment = {\r\n start: {\r\n row: 0,\r\n byte: 0,\r\n },\r\n end: {\r\n row: 0,\r\n byte: 0,\r\n },\r\n }\r\n }\r\n\r\n /**\r\n * Get the total number of rows used by this {@link BufferElement}\r\n * @readonly\r\n */\r\n get rowCount(): number {\r\n return this.alignment.end.row - this.alignment.start.row + 1\r\n }\r\n\r\n /**\r\n * Get the total number of bytes used by this {@link BufferElement} based on {@link BufferElementAlignment | alignment} start and end offsets\r\n * @readonly\r\n */\r\n get byteCount(): number {\r\n return Math.abs(this.endOffset - this.startOffset) + 1\r\n }\r\n\r\n /**\r\n * Get the total number of bytes used by this {@link BufferElement}, including final padding\r\n * @readonly\r\n */\r\n get paddedByteCount(): number {\r\n return (this.alignment.end.row + 1) * bytesPerRow\r\n }\r\n\r\n /**\r\n * Get the offset (i.e. byte index) at which our {@link BufferElement} starts\r\n * @readonly\r\n */\r\n get startOffset(): number {\r\n return this.getByteCountAtPosition(this.alignment.start)\r\n }\r\n\r\n /**\r\n * Get the array offset (i.e. array index) at which our {@link BufferElement} starts\r\n * @readonly\r\n */\r\n get startOffsetToIndex(): number {\r\n return this.startOffset / bytesPerSlot\r\n }\r\n\r\n /**\r\n * Get the offset (i.e. byte index) at which our {@link BufferElement} ends\r\n * @readonly\r\n */\r\n get endOffset(): number {\r\n return this.getByteCountAtPosition(this.alignment.end)\r\n }\r\n\r\n /**\r\n * Get the array offset (i.e. array index) at which our {@link BufferElement} ends\r\n * @readonly\r\n */\r\n get endOffsetToIndex(): number {\r\n return Math.floor(this.endOffset / bytesPerSlot)\r\n }\r\n\r\n /**\r\n * Get the position at given offset (i.e. byte index)\r\n * @param offset - byte index to use\r\n */\r\n getPositionAtOffset(offset = 0): BufferElementAlignmentPosition {\r\n return {\r\n row: Math.floor(offset / bytesPerRow),\r\n byte: offset % bytesPerRow,\r\n }\r\n }\r\n\r\n /**\r\n * Get the number of bytes at a given {@link BufferElementAlignmentPosition | position}\r\n * @param position - {@link BufferElementAlignmentPosition | position} from which to count\r\n * @returns - byte count at the given {@link BufferElementAlignmentPosition | position}\r\n */\r\n getByteCountAtPosition(position: BufferElementAlignmentPosition = { row: 0, byte: 0 }): number {\r\n return position.row * bytesPerRow + position.byte\r\n }\r\n\r\n /**\r\n * Check that a {@link BufferElementAlignmentPosition#byte | byte position} does not overflow its max value (16)\r\n * @param position - {@link BufferElementAlignmentPosition | position}\r\n * @returns - updated {@link BufferElementAlignmentPosition | position}\r\n */\r\n applyOverflowToPosition(\r\n position: BufferElementAlignmentPosition = { row: 0, byte: 0 }\r\n ): BufferElementAlignmentPosition {\r\n if (position.byte > bytesPerRow - 1) {\r\n const overflow = position.byte % bytesPerRow\r\n position.row += Math.floor(position.byte / bytesPerRow)\r\n position.byte = overflow\r\n }\r\n\r\n return position\r\n }\r\n\r\n /**\r\n * Get the number of bytes between two {@link BufferElementAlignmentPosition | positions}\r\n * @param p1 - first {@link BufferElementAlignmentPosition | position}\r\n * @param p2 - second {@link BufferElementAlignmentPosition | position}\r\n * @returns - number of bytes\r\n */\r\n getByteCountBetweenPositions(\r\n p1: BufferElementAlignmentPosition = { row: 0, byte: 0 },\r\n p2: BufferElementAlignmentPosition = { row: 0, byte: 0 }\r\n ): number {\r\n return Math.abs(this.getByteCountAtPosition(p2) - this.getByteCountAtPosition(p1))\r\n }\r\n\r\n /**\r\n * Compute the right alignment (i.e. start and end rows and bytes) given the size and align properties and the next available {@link BufferElementAlignmentPosition | position}\r\n * @param nextPositionAvailable - next {@link BufferElementAlignmentPosition | position} at which we should insert this element\r\n * @returns - computed {@link BufferElementAlignment | alignment}\r\n */\r\n getElementAlignment(\r\n nextPositionAvailable: BufferElementAlignmentPosition = { row: 0, byte: 0 }\r\n ): BufferElementAlignment {\r\n const alignment = {\r\n start: nextPositionAvailable,\r\n end: nextPositionAvailable,\r\n }\r\n\r\n const { size, align } = this.bufferLayout\r\n\r\n // check the alignment, i.e. even if there's enough space for our binding\r\n // we might have to pad the slot because some types need a specific alignment\r\n if (nextPositionAvailable.byte % align !== 0) {\r\n nextPositionAvailable.byte += nextPositionAvailable.byte % align\r\n }\r\n\r\n // in the case of a binding that could fit on one row\r\n // but we don't have space on the current row for this binding element\r\n // go to next row\r\n if (size <= bytesPerRow && nextPositionAvailable.byte + size > bytesPerRow) {\r\n nextPositionAvailable.row += 1\r\n nextPositionAvailable.byte = 0\r\n }\r\n\r\n alignment.end = {\r\n row: nextPositionAvailable.row + Math.ceil(size / bytesPerRow) - 1,\r\n byte: nextPositionAvailable.byte + (size % bytesPerRow === 0 ? bytesPerRow - 1 : (size % bytesPerRow) - 1), // end of row ? then it ends on slot (bytesPerRow - 1)\r\n }\r\n\r\n // now final check, if end slot has overflown\r\n alignment.end = this.applyOverflowToPosition(alignment.end)\r\n\r\n return alignment\r\n }\r\n\r\n /**\r\n * Set the {@link BufferElementAlignment | alignment} from a {@link BufferElementAlignmentPosition | position}\r\n * @param position - {@link BufferElementAlignmentPosition | position} at which to start inserting the values in the {@link !core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n */\r\n setAlignmentFromPosition(position: BufferElementAlignmentPosition = { row: 0, byte: 0 }) {\r\n this.alignment = this.getElementAlignment(position)\r\n }\r\n\r\n /**\r\n * Set the {@link BufferElementAlignment | alignment} from an offset (byte count)\r\n * @param startOffset - offset at which to start inserting the values in the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n */\r\n setAlignment(startOffset = 0) {\r\n this.setAlignmentFromPosition(this.getPositionAtOffset(startOffset))\r\n }\r\n\r\n /**\r\n * Set the {@link view}\r\n * @param arrayBuffer - the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n * @param arrayView - the {@link core/bindings/BufferBinding.BufferBinding#arrayView | buffer binding array view}\r\n */\r\n setView(arrayBuffer: ArrayBuffer, arrayView: DataView) {\r\n this.view = new this.bufferLayout.View(\r\n arrayBuffer,\r\n this.startOffset,\r\n this.byteCount / this.bufferLayout.View.BYTES_PER_ELEMENT\r\n )\r\n }\r\n\r\n /**\r\n * Update the {@link view} based on the new value\r\n * @param value - new value to use\r\n */\r\n update(value) {\r\n if (this.type === 'f32' || this.type === 'u32' || this.type === 'i32') {\r\n this.view[0] = value as number\r\n } else if (this.type === 'vec2f') {\r\n this.view[0] = (value as Vec2).x ?? value[0] ?? 0\r\n this.view[1] = (value as Vec2).y ?? value[1] ?? 0\r\n } else if (this.type === 'vec3f') {\r\n this.view[0] = (value as Vec3).x ?? value[0] ?? 0\r\n this.view[1] = (value as Vec3).y ?? value[1] ?? 0\r\n this.view[2] = (value as Vec3).z ?? value[2] ?? 0\r\n } else if ((value as Quat | Mat4).elements) {\r\n this.view.set((value as Quat | Mat4).elements)\r\n } else if (ArrayBuffer.isView(value) || Array.isArray(value)) {\r\n this.view.set(value as number[])\r\n }\r\n }\r\n\r\n /**\r\n * Extract the data corresponding to this specific {@link BufferElement} from a {@link Float32Array} holding the {@link GPUBuffer} data of the parentMesh {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}\r\n * @param result - {@link Float32Array} holding {@link GPUBuffer} data\r\n * @returns - extracted data from the {@link Float32Array}\r\n */\r\n extractDataFromBufferResult(result: Float32Array) {\r\n return result.slice(this.startOffsetToIndex, this.endOffsetToIndex)\r\n }\r\n}\r\n","import { BufferElement, BufferElementParams, bytesPerSlot } from './BufferElement'\nimport { throwWarning } from '../../../utils/utils'\n\n/**\n * Parameters used to create a {@link BufferArrayElement}\n */\nexport interface BufferArrayElementParams extends BufferElementParams {\n /** Initial length of the input {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array} */\n arrayLength: number\n}\n\n/**\n * Used to handle specific array {@link core/bindings/BufferBinding.BufferBinding | BufferBinding} types\n */\nexport class BufferArrayElement extends BufferElement {\n /** Initial length of the input {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array} */\n arrayLength: number\n /** Total number of elements (i.e. {@link arrayLength} divided by {@link core/bindings/utils.BufferLayout | buffer layout} number of elements */\n numElements: number\n /** Number of bytes in the {@link ArrayBuffer} between two elements {@link startOffset} */\n arrayStride: number\n\n /**\n * BufferArrayElement constructor\n * @param parameters - {@link BufferArrayElementParams | parameters} used to create our {@link BufferArrayElement}\n */\n constructor({ name, key, type = 'f32', arrayLength = 1 }: BufferArrayElementParams) {\n super({ name, key, type })\n\n this.arrayLength = arrayLength\n this.numElements = this.arrayLength / this.bufferLayout.numElements\n }\n\n /**\n * Get the array stride between two elements of the array, in indices\n * @readonly\n */\n get arrayStrideToIndex(): number {\n return this.arrayStride / bytesPerSlot\n }\n\n /**\n * Set the {@link core/bindings/bufferElements/BufferElement.BufferElementAlignment | alignment}\n * To compute how arrays are packed, we get the second item alignment as well and use it to calculate the arrayStride between two array elements. Using the arrayStride and the total number of elements, we can easily get the end alignment position.\n * @param startOffset - offset at which to start inserting the values in the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array buffer}\n */\n setAlignment(startOffset = 0) {\n super.setAlignment(startOffset)\n\n // repeat for a second element to know how things are laid out\n const nextAlignment = this.getElementAlignment(this.getPositionAtOffset(this.endOffset + 1))\n this.arrayStride = this.getByteCountBetweenPositions(this.alignment.end, nextAlignment.end)\n\n this.alignment.end = this.getPositionAtOffset(this.endOffset + this.arrayStride * (this.numElements - 1))\n }\n\n /**\n * Update the {@link view} based on the new value\n * @param value - new value to use\n */\n update(value) {\n if (ArrayBuffer.isView(value) || Array.isArray(value)) {\n let valueIndex = 0\n\n const viewLength = this.byteCount / this.bufferLayout.View.BYTES_PER_ELEMENT\n // arrayStride is our view length divided by the number of elements in our array\n const stride = Math.ceil(viewLength / this.numElements)\n\n for (let i = 0; i < this.numElements; i++) {\n for (let j = 0; j < this.bufferLayout.numElements; j++) {\n this.view[j + i * stride] = value[valueIndex]\n\n valueIndex++\n }\n }\n } else {\n throwWarning(`BufferArrayElement: value passed to ${this.name} is not an array: ${value}`)\n }\n }\n}\n","import { BufferArrayElement, BufferArrayElementParams } from './BufferArrayElement'\r\n\r\n/**\r\n * Used to compute alignment when dealing with arrays of Struct\r\n */\r\nexport class BufferInterleavedArrayElement extends BufferArrayElement {\r\n /** Corresponding {@link DataView} set function based on {@link view} type */\r\n viewSetFunction: DataView['setInt32'] | DataView['setUint16'] | DataView['setUint32'] | DataView['setFloat32']\r\n\r\n /**\r\n * BufferInterleavedArrayElement constructor\r\n * @param parameters - {@link BufferArrayElementParams | parameters} used to create our {@link BufferInterleavedArrayElement}\r\n */\r\n constructor({ name, key, type = 'f32', arrayLength = 1 }: BufferArrayElementParams) {\r\n super({ name, key, type, arrayLength })\r\n\r\n this.arrayStride = 1\r\n\r\n this.arrayLength = arrayLength\r\n this.numElements = this.arrayLength / this.bufferLayout.numElements\r\n }\r\n\r\n /**\r\n * Get the total number of slots used by this {@link BufferInterleavedArrayElement} based on buffer layout size and total number of elements\r\n * @readonly\r\n */\r\n get byteCount(): number {\r\n return this.bufferLayout.size * this.numElements\r\n }\r\n\r\n /**\r\n * Set the {@link core/bindings/bufferElements/BufferElement.BufferElementAlignment | alignment}\r\n * To compute how arrays are packed, we need to compute the arrayStride between two elements beforehand and pass it here. Using the arrayStride and the total number of elements, we can easily get the end alignment position.\r\n * @param startOffset - offset at which to start inserting the values in the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n * @param stride - Stride in the {@link ArrayBuffer} between two elements of the array\r\n */\r\n setAlignment(startOffset = 0, stride = 0) {\r\n this.alignment = this.getElementAlignment(this.getPositionAtOffset(startOffset))\r\n\r\n this.arrayStride = stride\r\n\r\n this.alignment.end = this.getPositionAtOffset(this.endOffset + stride * (this.numElements - 1))\r\n }\r\n\r\n /**\r\n * Set the {@link view} and {@link viewSetFunction}\r\n * @param arrayBuffer - the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n * @param arrayView - the {@link core/bindings/BufferBinding.BufferBinding#arrayView | buffer binding array view}\r\n */\r\n setView(arrayBuffer: ArrayBuffer, arrayView: DataView) {\r\n // our view will be a simple typed array, not linked to the array buffer\r\n this.view = new this.bufferLayout.View(this.bufferLayout.numElements * this.numElements)\r\n\r\n // but our viewSetFunction is linked to the array view\r\n this.viewSetFunction = ((arrayView) => {\r\n switch (this.bufferLayout.View) {\r\n case Int32Array:\r\n return arrayView.setInt32.bind(arrayView) as DataView['setInt32']\r\n case Uint16Array:\r\n return arrayView.setUint16.bind(arrayView) as DataView['setUint16']\r\n case Uint32Array:\r\n return arrayView.setUint32.bind(arrayView) as DataView['setUint32']\r\n case Float32Array:\r\n default:\r\n return arrayView.setFloat32.bind(arrayView) as DataView['setFloat32']\r\n }\r\n })(arrayView)\r\n }\r\n\r\n /**\r\n * Update the {@link view} based on the new value, and then update the {@link core/bindings/BufferBinding.BufferBinding#arrayView | buffer binding array view} using sub arrays\r\n * @param value - new value to use\r\n */\r\n update(value) {\r\n super.update(value)\r\n\r\n // now use our viewSetFunction to fill the array view with interleaved alignment\r\n for (let i = 0; i < this.numElements; i++) {\r\n const subarray = this.view.subarray(\r\n i * this.bufferLayout.numElements,\r\n i * this.bufferLayout.numElements + this.bufferLayout.numElements\r\n )\r\n\r\n const startByteOffset = this.startOffset + i * this.arrayStride\r\n\r\n // view set function need to be called for each subarray entry, so loop over subarray entries\r\n subarray.forEach((value, index) => {\r\n this.viewSetFunction(startByteOffset + index * this.bufferLayout.View.BYTES_PER_ELEMENT, value, true)\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Extract the data corresponding to this specific {@link BufferInterleavedArrayElement} from a {@link Float32Array} holding the {@link GPUBuffer} data of the parentMesh {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}\r\n * @param result - {@link Float32Array} holding {@link GPUBuffer} data\r\n */\r\n extractDataFromBufferResult(result: Float32Array) {\r\n const interleavedResult = new Float32Array(this.arrayLength)\r\n for (let i = 0; i < this.numElements; i++) {\r\n const resultOffset = this.startOffsetToIndex + i * this.arrayStrideToIndex\r\n\r\n for (let j = 0; j < this.bufferLayout.numElements; j++) {\r\n interleavedResult[i * this.bufferLayout.numElements + j] = result[resultOffset + j]\r\n }\r\n }\r\n return interleavedResult\r\n }\r\n}\r\n","import { Binding, BindingParams, BufferBindingMemoryAccessType } from './Binding'\nimport { getBindGroupLayoutBindingType, getBindingWGSLVarType, getBufferLayout, TypedArray } from './utils'\nimport { throwWarning, toCamelCase, toKebabCase } from '../../utils/utils'\nimport { Vec2 } from '../../math/Vec2'\nimport { Vec3 } from '../../math/Vec3'\nimport { Input, InputBase, InputValue } from '../../types/BindGroups'\nimport { BufferElement } from './bufferElements/BufferElement'\nimport { BufferArrayElement } from './bufferElements/BufferArrayElement'\nimport { BufferInterleavedArrayElement } from './bufferElements/BufferInterleavedArrayElement'\n\n/**\n * Defines a {@link BufferBinding} input object that can set a value and run a callback function when this happens\n */\nexport interface BufferBindingInput extends InputBase {\n /** Original {@link InputValue | input value} */\n _value: InputValue\n\n /** Get the {@link InputValue | input value} */\n get value(): InputValue\n\n /** Set the {@link InputValue | input value} */\n set value(value: InputValue)\n\n /** Whether the {@link InputValue | input value} has changed and we should update the {@link BufferBinding#arrayBuffer | buffer binding array} */\n shouldUpdate: boolean\n}\n\n/**\n * Base parameters used to create a {@link BufferBinding}\n */\nexport interface BufferBindingBaseParams {\n /** Whether this {@link BufferBinding} should use structured WGSL variables */\n useStruct?: boolean\n /** {@link BufferBinding} memory access types (read only or read/write) */\n access?: BufferBindingMemoryAccessType\n /** Object containing one or multiple {@link Input | inputs} describing the structure of the {@link BufferBinding} */\n struct?: Record