import { createIndicesForQuads } from '@pixi/utils'; import { Geometry, Buffer } from '@pixi/core'; import { TYPES } from '@pixi/constants'; /** * @author Mat Groves * * Big thanks to the very clever Matt DesLauriers <mattdesl> https://github.com/mattdesl/ * for creating the original PixiJS version! * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that * they now share 4 bytes on the vertex buffer * * Heavily inspired by LibGDX's ParticleBuffer: * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleBuffer.java */ /** * The particle buffer manages the static and dynamic buffers for a particle container. * * @class * @private * @memberof PIXI */ export class ParticleBuffer { /** * @private * @param {object} properties - The properties to upload. * @param {boolean[]} dynamicPropertyFlags - Flags for which properties are dynamic. * @param {number} size - The size of the batch. */ constructor(properties, dynamicPropertyFlags, size) { this.geometry = new Geometry(); this.indexBuffer = null; /** * The number of particles the buffer can hold * * @private * @member {number} */ this.size = size; /** * A list of the properties that are dynamic. * * @private * @member {object[]} */ this.dynamicProperties = []; /** * A list of the properties that are static. * * @private * @member {object[]} */ this.staticProperties = []; for (let i = 0; i < properties.length; ++i) { let property = properties[i]; // Make copy of properties object so that when we edit the offset it doesn't // change all other instances of the object literal property = { attributeName: property.attributeName, size: property.size, uploadFunction: property.uploadFunction, type: property.type || TYPES.FLOAT, offset: property.offset, }; if (dynamicPropertyFlags[i]) { this.dynamicProperties.push(property); } else { this.staticProperties.push(property); } } this.staticStride = 0; this.staticBuffer = null; this.staticData = null; this.staticDataUint32 = null; this.dynamicStride = 0; this.dynamicBuffer = null; this.dynamicData = null; this.dynamicDataUint32 = null; this._updateID = 0; this.initBuffers(); } /** * Sets up the renderer context and necessary buffers. * * @private */ initBuffers() { const geometry = this.geometry; let dynamicOffset = 0; /** * Holds the indices of the geometry (quads) to draw * * @member {Uint16Array} * @private */ this.indexBuffer = new Buffer(createIndicesForQuads(this.size), true, true); geometry.addIndex(this.indexBuffer); this.dynamicStride = 0; for (let i = 0; i < this.dynamicProperties.length; ++i) { const property = this.dynamicProperties[i]; property.offset = dynamicOffset; dynamicOffset += property.size; this.dynamicStride += property.size; } const dynBuffer = new ArrayBuffer(this.size * this.dynamicStride * 4 * 4); this.dynamicData = new Float32Array(dynBuffer); this.dynamicDataUint32 = new Uint32Array(dynBuffer); this.dynamicBuffer = new Buffer(this.dynamicData, false, false); // static // let staticOffset = 0; this.staticStride = 0; for (let i = 0; i < this.staticProperties.length; ++i) { const property = this.staticProperties[i]; property.offset = staticOffset; staticOffset += property.size; this.staticStride += property.size; } const statBuffer = new ArrayBuffer(this.size * this.staticStride * 4 * 4); this.staticData = new Float32Array(statBuffer); this.staticDataUint32 = new Uint32Array(statBuffer); this.staticBuffer = new Buffer(this.staticData, true, false); for (let i = 0; i < this.dynamicProperties.length; ++i) { const property = this.dynamicProperties[i]; geometry.addAttribute( property.attributeName, this.dynamicBuffer, 0, property.type === TYPES.UNSIGNED_BYTE, property.type, this.dynamicStride * 4, property.offset * 4 ); } for (let i = 0; i < this.staticProperties.length; ++i) { const property = this.staticProperties[i]; geometry.addAttribute( property.attributeName, this.staticBuffer, 0, property.type === TYPES.UNSIGNED_BYTE, property.type, this.staticStride * 4, property.offset * 4 ); } } /** * Uploads the dynamic properties. * * @private * @param {PIXI.DisplayObject[]} children - The children to upload. * @param {number} startIndex - The index to start at. * @param {number} amount - The number to upload. */ uploadDynamic(children, startIndex, amount) { for (let i = 0; i < this.dynamicProperties.length; i++) { const property = this.dynamicProperties[i]; property.uploadFunction(children, startIndex, amount, property.type === TYPES.UNSIGNED_BYTE ? this.dynamicDataUint32 : this.dynamicData, this.dynamicStride, property.offset); } this.dynamicBuffer._updateID++; } /** * Uploads the static properties. * * @private * @param {PIXI.DisplayObject[]} children - The children to upload. * @param {number} startIndex - The index to start at. * @param {number} amount - The number to upload. */ uploadStatic(children, startIndex, amount) { for (let i = 0; i < this.staticProperties.length; i++) { const property = this.staticProperties[i]; property.uploadFunction(children, startIndex, amount, property.type === TYPES.UNSIGNED_BYTE ? this.staticDataUint32 : this.staticData, this.staticStride, property.offset); } this.staticBuffer._updateID++; } /** * Destroys the ParticleBuffer. * * @private */ destroy() { this.indexBuffer = null; this.dynamicProperties = null; // this.dynamicBuffer.destroy(); this.dynamicBuffer = null; this.dynamicData = null; this.dynamicDataUint32 = null; this.staticProperties = null; // this.staticBuffer.destroy(); this.staticBuffer = null; this.staticData = null; this.staticDataUint32 = null; // all buffers are destroyed inside geometry this.geometry.destroy(); } }