Source: /Users/shukantpal/Web Projects/pixi-batch-renderer/lib/pixi-batch-renderer.mjs

/Users/shukantpal/Web Projects/pixi-batch-renderer/lib/pixi-batch-renderer.mjs

/*!
 * pixi-batch-renderer
 * Compiled Sun, 12 Apr 2020 20:13:19 UTC
 *
 * pixi-batch-renderer is licensed under the MIT License.
 * http://www.opensource.org/licenses/mit-license
 */
import { ViewableBuffer, Geometry, Buffer, TYPES, utils, ObjectRenderer, State, settings, ENV, Shader, Point, Matrix, BaseTexture } from 'pixi.js';

/**
 * Redirects are used to aggregate the resources needed by the WebGL pipeline to render
 * a display-object. This includes the base primitives (geometry), uniforms, and
 * textures (which are handled as "special" uniforms).
 *
 * @memberof PIXI.brend
 * @class
 * @abstract
 * @see PIXI.brend.AttributeRedirect
 */
class Redirect {
    constructor(source, glslIdentifer) {
        /**
         * The property on the display-object that holds the resource.
         *
         * Instead of a property, you can provide a callback that generates the resource
         * on invokation.
         *
         * @member {string | Function}
         */
        this.source = source;
        /**
         * The shader variable that references the resource, e.g. attribute or uniform
         * name.
         * @member {string}
         */
        this.glslIdentifer = glslIdentifer;
    }
}

/**
 * This redirect defines an attribute of a display-object's geometry. The attribute
 * data is expected to be stored in a `PIXI.ViewableBuffer`, in an array, or (if
 * just one element) as the property itself.
 *
 * @memberof PIXI.brend
 * @class
 * @extends PIXI.brend.Redirect
 * @example
 * // This attribute redirect calculates the tint used on top of a texture. Since the
 * // tintMode can change anytime, it is better to use a derived source (function).
 * //
 * // Furthermore, the color is uploaded as four bytes (`attribute vec4 aTint`) while the
 * // source returns an integer. This is done by splitting the 32-bit integer into four
 * // 8-bit bytes.
 * new PIXI.brend.AttributeRedirect({
 *     source: (tgt: ExampleDisplay) => (tgt.alpha < 1.0 && tgt.tintMode === PREMULTIPLY)
 *          ? premultiplyTint(tgt.rgb, tgt.alpha)
 *          : tgt.rgb + (tgt.alpha << 24);
 *     attrib: 'aTint',
 *     type: 'int32',
 *     size: '%notarray%', // optional/default
 *     glType: PIXI.TYPES.UNSIGNED_BYTE,
 *     glSize: 4,
 *     normalize: true // We are using [0, 255] range for RGBA here. Must normalize to [0, 1].
 * });
 */
class AttributeRedirect extends Redirect {
    /**
     * @param {object} options
     * @param {string | Function} options.source - redirect source
     * @param {string} options.attrib - shader attribute variable
     * @param {string}[options.type='float32'] - the type of data stored in the source
     * @param {number | '%notarray%'}[options.size=0] - size of the source array ('%notarray' if not an array & just one element)
     * @param {PIXI.TYPES}[options.glType=PIXI.TYPES.FLOAT] - data format to be uploaded in
     * @param {number} options.glSize - number of elements to be uploaded as (size of source and upload must match)
     * @param {boolean}[options.normalize=false] - whether to normalize the data before uploading
     */
    constructor(options) {
        super(options.source, options.attrib);
        /**
         * The type of data stored in the source buffer. This can be any of: `int8`, `uint8`,
         * `int16`, `uint16`, `int32`, `uint32`, or (by default) `float32`.
         *
         * @member {string}
         * @see [PIXI.ViewableBuffer#view]{@link https://pixijs.download/dev/docs/PIXI.ViewableBuffer.html}
         * @default 'float32'
         */
        this.type = options.type;
        /**
         * Number of elements to extract out of `source` with
         * the given view type, for one vertex.
         *
         * If source isn't an array (only one element), then
         * you can set this to `'%notarray%'`.
         *
         * @member {number | '%notarray%'}
         */
        this.size = options.size;
        /**
         * This is equal to `size` or 1 if size is `%notarray%`.
         *
         * @member {number}
         */
        this.properSize = (options.size === '%notarray%' || options.size === undefined) ? 1 : options.size;
        /**
         * Type of attribute, when uploading.
         *
         * Normally, you would use the corresponding type for
         * the view on source. However, to speed up uploads
         * you can aggregate attribute values in larger data
         * types. For example, an RGBA vec4 (byte-sized channels)
         * can be represented as one `Uint32`, while having
         * a `glType` of `UNSIGNED_BYTE`.
         *
         * @member {PIXI.TYPES}
         */
        this.glType = options.glType;
        /**
         * Size of attribute in terms of `glType`.
         *
         * Note that `glSize * glType <= size * type`
         *
         * @readonly
         */
        this.glSize = options.glSize;
        /**
         * Whether to normalize the attribute values.
         *
         * @member {boolean}
         * @readonly
         */
        this.normalize = !!options.normalize;
    }
    static vertexSizeFor(attributeRedirects) {
        return attributeRedirects.reduce((acc, redirect) => (ViewableBuffer.sizeOf(redirect.type)
            * redirect.properSize)
            + acc, 0);
    }
}

/**
 * This redirect is used to aggregate & upload uniforms required for shading the
 * display-object.
 *
 * @memberof PIXI.brend
 * @class
 * @extends PIXI.brend.Redirect
 * @example
 * // The data-type of this uniform is defined in your shader.
 * new PIXI.brend.UniformRedirect({
 *      source: (dob: PIXI.DisplayObject) => dob.transform.worldTransform,
 *      uniform: "transform"
 * });
 */
class UniformRedirect extends Redirect {
    constructor(options) {
        super(options.source, options.uniform);
    }
}

/**
 * Resources that need to be uploaded to WebGL to render one batch.
 *
 * To customize batches, you must create your own batch factory by extending the
 * `PIXI.brend.StdBatchFactory` class.
 *
 * @memberof PIXI.brend
 * @class
 * @see PIXI.brend.StdBatchFactory
 */
class StdBatch {
    constructor(geometryOffset) {
        /**
         * Index of the first vertex of this batch's geometry in the uploaded geometry.
         *
         * @member {number}
         */
        this.geometryOffset = geometryOffset;
        /**
         * Textures that are used by the display-object's in this batch.
         *
         * @member {Array<PIXI.Texture>}
         */
        this.textureBuffer = null;
        /**
         * Map of base-texture UIDs to texture indices into `uSamplers`.
         *
         * @member {Map<number, number>}
         */
        this.uidMap = null;
        /**
         * State required to render this batch.
         *
         * @member {PIXI.State}
         */
        this.state = null;
    }
    /**
     * Uploads the resources required before rendering this batch. If you override
     * this, you must call `super.upload`.
     *
     * @param {PIXI.Renderer} renderer
     */
    upload(renderer) {
        this.textureBuffer.forEach((tex, i) => {
            renderer.texture.bind(tex, i);
        });
        renderer.state.set(this.state);
    }
    /**
     * Reset this batch to become "fresh"!
     */
    reset() {
        this.textureBuffer = this.uidMap = this.state = null;
        if (this.batchBuffer) {
            this.batchBuffer.length = 0;
        }
    }
}

/**
 * Factory for producing "standard" (based on state, geometry, & textures) batches of
 * display-objects.
 *
 * **NOTE:** Instead of "building" batches, this factory actually keeps the batches in
 * a buffer so they can be accessed together at the end.
 *
 * **Shared Textures**: If display-objects in the same batch use the same base-texture,
 * then that base-texture is not uploaded twice. This allows for more better batch density
 * when you use texture atlases (textures with same base-texture). This is one reason why
 * textures are treated as "special" uniforms.
 *
 * @memberof PIXI.brend
 * @class
 * @see PIXI.brend.AggregateUniformsBatchFactory
 */
class StdBatchFactory {
    /**
     * @param {PIXI.brend.BatchRenderer} renderer
     */
    constructor(renderer) {
        /**
         * @member {PIXI.brend.BatchRenderer}
         * @protected
         */
        this._renderer = renderer;
        this._state = null;
        /**
         * Textures per display-object
         * @member {number}
         */
        this._textureCount = renderer._texturesPerObject;
        /**
         * Property in which textures are kept of display-objects
         * @member {string}
         */
        this._textureProperty = renderer._textureProperty;
        /**
         * Max. no of textures per batch (should be <= texture units of GPU)
         * @member {number}
         */
        this._textureLimit = renderer.MAX_TEXTURES;
        /**
         * @member {object}
         */
        this._textureBuffer = {}; // uid : texture map
        this._textureBufferLength = 0;
        this._textureIndexedBuffer = []; // array of textures
        this._textureIndexMap = {}; // uid : index in above
        /**
         * Display-objects in current batch
         * @protected
         */
        this._batchBuffer = [];
        /**
         * Pool to batch objects into which data is fed.
         * @member {any[]}
         * @protected
         */
        this._batchPool = [];
        /**
         * Number of batches created since last reset.
         * @member {number}
         * @protected
         */
        this._batchCount = 0;
        if (this._textureCount === 1) {
            this._putTexture = this._putSingleTexture;
        }
        else {
            this._putTexture = this._putAllTextures;
        }
    }
    /**
     * Puts the display-object into the current batch, if possible.
     *
     * @param targetObject {PIXI.DisplayObject} - object to add
     * @param state {PIXI.State} - state required by that object
     * @return {boolean} whether the object was added to the batch. If it wasn't, you should "build" it.
     */
    put(targetObject, state) {
        // State compat
        if (!this._state) {
            this._state = state;
        }
        else if (this._state.data !== state.data) {
            return false;
        }
        // Customized compat
        if (!this._put(targetObject)) {
            return false;
        }
        // Texture compat
        if (this._textureCount > 0 && !this._putTexture(targetObject[this._textureProperty])) {
            return false;
        }
        this._batchBuffer.push(targetObject);
        return true;
    }
    /**
     * Creates the batch object and pushes it into the pool This also resets any state
     * so that a new batch can be started again.
     *
     * @param batch {PIXI.brend.Batch}
     */
    build(geometryOffset) {
        const batch = this._nextBatch();
        batch.geometryOffset = geometryOffset;
        this._buildBatch(batch);
        this._state = null;
        this._batchBuffer = [];
        this._textureBuffer = {};
        this._textureIndexMap = {};
        this._textureBufferLength = 0;
        this._textureIndexedBuffer = [];
    }
    /**
     * @returns {boolean} - whether this factory is ready to start a new batch from
     *  "start". If not, then the current batch must be built before starting a new one.
     */
    ready() {
        return this._batchBuffer.length === 0;
    }
    /**
     * Clears the batch pool.
     */
    reset() {
        this._batchCount = 0;
    }
    /**
     * Returns the built batch pool. The array returned may be larger than the pool
     * itself.
     *
     * @returns {Array<object>}
     */
    access() {
        return this._batchPool;
    }
    /**
     * Size of the batch pool built since last reset.
     */
    size() {
        return this._batchCount;
    }
    /**
     * Should store any information from the display-object to be put into
     * the batch.
     * @param {PIXI.DisplayObject} displayObject
     * @returns {boolean} - whether the display-object was "compatible" with
     *      other display-objects in the batch. If not, it should not have been
     *      added.
     */
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    _put(displayObject) {
        // Override this
        return true;
    }
    /**
     * @returns {object} a new batch
     * @protected
     * @example
     * _newBatch(): CustomBatch
     * {
     *      return new CustomBatch();
     * }
     */
    _newBatch() {
        return new StdBatch();
    }
    /**
     * @param {number} geometryOffset
     */
    _nextBatch(geometryOffset) {
        if (this._batchCount === this._batchPool.length) {
            this._batchPool.push(this._newBatch());
        }
        const batch = this._batchPool[this._batchCount++];
        batch.reset();
        batch.geometryOffset = geometryOffset;
        return batch;
    }
    /**
     * Should add any information required to render the batch. If you override this,
     * you must call `super._buildBatch` and clear any state.
     * @param {object} batch
     * @protected
     * @example
     * _buildBatch(batch: any): void
     * {
     *      super._buildBatch(batch);
     *      batch.depth = this.generateDepth();
     *
     *      // if applicable
     *      this.resetDepthGenerator();
     * }
     */
    _buildBatch(batch) {
        batch.batchBuffer = this._batchBuffer;
        batch.textureBuffer = this._textureIndexedBuffer;
        batch.uidMap = this._textureIndexMap;
        batch.state = this._state;
    }
    // Optimized _putTexture case.
    _putSingleTexture(texture) {
        if ('baseTexture' in texture) {
            texture = texture.baseTexture;
        }
        const baseTexture = texture;
        if (this._textureBuffer[baseTexture.uid]) {
            return true;
        }
        else if (this._textureBufferLength + 1 <= this._textureLimit) {
            this._textureBuffer[baseTexture.uid] = texture;
            this._textureBufferLength += 1;
            const newLength = this._textureIndexedBuffer.push(baseTexture);
            const index = newLength - 1;
            this._textureIndexMap[baseTexture.uid] = index;
            return true;
        }
        return false;
    }
    _putAllTextures(textureArray) {
        let deltaBufferLength = 0;
        for (let i = 0; i < textureArray.length; i++) {
            const texture = (textureArray[i].baseTexture
                ? textureArray[i].baseTexture
                : textureArray[i]);
            if (!this._textureBuffer[texture.uid]) {
                ++deltaBufferLength;
            }
        }
        if (deltaBufferLength + this._textureBufferLength > this._textureLimit) {
            return false;
        }
        for (let i = 0; i < textureArray.length; i++) {
            const texture = textureArray[i].baseTexture
                ? textureArray[i].baseTexture
                : textureArray[i];
            if (!this._textureBuffer[texture.uid]) {
                this._textureBuffer[texture.uid] = texture;
                this._textureBufferLength += 1;
                const newLength = this._textureIndexedBuffer.push(texture);
                const index = newLength - 1;
                this._textureIndexMap[texture.uid] = index;
            }
        }
        return true;
    }
}

// BatchGeometryFactory uses this class internally to setup the attributes of
// the batches.
//
// Supports Uniforms+Standard Pipeline's in-batch/uniform ID.
class BatchGeometry extends Geometry {
    constructor(attributeRedirects, hasIndex, texIDAttrib, texturesPerObject, inBatchIDAttrib, uniformIDAttrib) {
        super();
        const attributeBuffer = new Buffer(null, false, false);
        const indexBuffer = hasIndex ? new Buffer(null, false, true) : null;
        attributeRedirects.forEach((redirect) => {
            const { glslIdentifer, glType, glSize, normalize } = redirect;
            this.addAttribute(glslIdentifer, attributeBuffer, glSize, normalize, glType);
        });
        if (texIDAttrib && texturesPerObject > 0) {
            this.addAttribute(texIDAttrib, attributeBuffer, texturesPerObject, true, TYPES.FLOAT);
        }
        if (inBatchIDAttrib) {
            this.addAttribute(inBatchIDAttrib, attributeBuffer, 1, false, TYPES.FLOAT);
        }
        if (uniformIDAttrib) {
            this.addAttribute(uniformIDAttrib, attributeBuffer, 1, false, TYPES.FLOAT);
        }
        if (hasIndex) {
            this.addIndex(indexBuffer);
        }
        this.attribBuffer = attributeBuffer;
        this.indexBuffer = indexBuffer;
    }
}
// To define the constructor shape, this is defined as an abstract class but documented
// as an interface.
class IBatchGeometryFactory {
    // eslint-disable-next-line @typescript-eslint/no-useless-constructor, @typescript-eslint/no-unused-vars
    constructor(renderer) {
        // Implementation
    }
}
/**
 * This interface defines the methods you need to implement to creating your own batch
 * geometry factory.
 *
 * The constructor of an implementation should take only one argument - the batch renderer.
 *
 * @memberof PIXI.brend
 * @interface IBatchGeometryFactory
 */
/**
 * Called before the batch renderer starts feeding the display-objects. This can be used
 * to pre-allocated space for the batch geometry.
 *
 * @memberof PIXI.brend.IBatchGeometryFactory#
 * @method init
 * @param {number} verticesBatched
 * @param {number}[indiciesBatched] - optional when display-object's don't use a index buffer
 */
/**
 * Adds the display-object to the batch geometry.
 *
 * If the display-object's shader also uses textures (in `uSamplers` uniform), then it will
 * be given a texture-ID to get the texture from the `uSamplers` array. If it uses multiple
 * textures, then the texture-ID is an array of indices into `uSamplers`. The texture-attrib
 * passed to the batch renderer sets the name of the texture-ID attribute (defualt is `aTextureId`).
 *
 * @memberof PIXI.brend.IBatchGeometryFactory#
 * @method append
 * @param {PIXI.DisplayObject} displayObject
 * @param {object} batch - the batch
 */
/**
 * This should wrap up the batch geometry in a `PIXI.Geometry` object.
 *
 * @memberof PIXI.brend.IBatchGeometryFactory#
 * @method build
 * @returns {PIXI.Geometry} batch geometry
 */
/**
 * This is used to return a batch geometry so it can be pooled and reused in a future `build()`
 * call.
 *
 * @memberof PIXI.brend.IBatchGeometryFactory#
 * @method release
 * @param {PIXI.Geometry} geom
 */
/**
 * Factory class that generates the geometry for a whole batch by feeding on
 * the individual display-object geometries. This factory is reusable, i.e. you
 * can build another geometry after a {@link build} call.
 *
 * **Optimizations:** To speed up geometry generation, this compiles an optimized
 * packing function that pushes attributes without looping through the attribute
 * redirects.
 *
 * **Default Format:** If you are not using a custom draw-call issuer, then
 * the batch geometry must have an interleaved attribute data buffer and one
 * index buffer.
 *
 * **Customization:** If you want to customize the batch geometry, then you must
 * also define your draw call issuer. This is not supported by pixi-batch-render
 * but is work-in-progress.
 *
 * **inBatchID Support**: If you specified an `inBatchID` attribute in the batch-renderer,
 * then this will support it automatically. The aggregate-uniforms pipeline doesn't need a custom
 * geometry factory.
 *
 * @memberof PIXI.brend
 * @class
 * @implements PIXI.brend.IBatchGeometryFactory
 */
class BatchGeometryFactory extends IBatchGeometryFactory {
    /**
     * @param {PIXI.brend.BatchRenderer} renderer
     */
    constructor(renderer) {
        super(renderer);
        this._targetCompositeAttributeBuffer = null;
        this._targetCompositeIndexBuffer = null;
        this._aIndex = 0;
        this._iIndex = 0;
        this._attribRedirects = renderer._attribRedirects;
        this._indexProperty = renderer._indexProperty;
        this._vertexCountProperty = renderer._vertexCountProperty;
        this._vertexSize = AttributeRedirect.vertexSizeFor(this._attribRedirects);
        this._texturesPerObject = renderer._texturesPerObject;
        this._textureProperty = renderer._textureProperty;
        this._texIDAttrib = renderer._texIDAttrib;
        this._inBatchIDAttrib = renderer._inBatchIDAttrib;
        this._uniformIDAttrib = renderer._uniformIDAttrib;
        this._vertexSize += this._texturesPerObject * 4; // texture indices are also passed
        if (this._inBatchIDAttrib) {
            this._vertexSize += 4;
        }
        if (this._uniformIDAttrib) {
            this._vertexSize += 4;
        }
        if (this._texturesPerObject === 1) {
            this._texID = 0;
        }
        else if (this._texturesPerObject > 1) {
            this._texID = new Array(this._texturesPerObject);
        }
        this._aBuffers = []; // @see _getAttributeBuffer
        this._iBuffers = []; // @see _getIndexBuffer
        /**
         * Batch geometries that can be reused.
         *
         * @member {PIXI.Geometry}
         * @protected
         * @see PIXI.brend.IBatchGeometryFactory#release
         */
        this._geometryPool = [];
    }
    /**
     * Ensures this factory has enough space to buffer the given number of vertices
     * and indices. This should be called before feeding display-objects from the
     * batch.
     *
     * @param {number} verticesBatched
     * @param {number} indiciesBatched
     */
    init(verticesBatched, indiciesBatched) {
        this._targetCompositeAttributeBuffer = this.getAttributeBuffer(verticesBatched);
        if (this._indexProperty) {
            this._targetCompositeIndexBuffer = this.getIndexBuffer(indiciesBatched);
        }
        this._aIndex = this._iIndex = 0;
    }
    /**
     * Append's the display-object geometry to this batch's geometry. You must override
     * this you need to "modify" the geometry of the display-object before merging into
     * the composite geometry (for example, adding an ID to a special uniform)
     *
     * @param {PIXI.DisplayObject} targetObject
     * @param {number} batch
     */
    append(targetObject, batch_) {
        const batch = batch_;
        const tex = targetObject[this._textureProperty];
        // GeometryMerger uses _texID for texIDAttrib
        if (this._texturesPerObject === 1) {
            const texUID = tex.baseTexture ? tex.baseTexture.uid : tex.uid;
            this._texID = batch.uidMap[texUID];
        }
        else if (this._texturesPerObject > 1) {
            let _tex;
            for (let k = 0; k < tex.length; k++) {
                _tex = tex[k];
                const texUID = _tex.BaseTexture ? _tex.baseTexture.uid : _tex.uid;
                this._texID[k] = batch.uidMap[texUID];
            }
        }
        // GeometryMerger uses this
        if (this._inBatchIDAttrib) {
            this._inBatchID = batch.batchBuffer.indexOf(targetObject);
        }
        if (this._uniformIDAttrib) {
            this._uniformID = batch.uniformMap[this._inBatchID];
        }
        this.geometryMerger(targetObject, this);
    }
    /**
     * @override
     * @returns {PIXI.Geometry} the generated batch geometry
     * @example
     * build(): PIXI.Geometry
     * {
     *      // Make sure you're not allocating new geometries if _geometryPool has some
     *      // already. (Otherwise, a memory leak will result!)
     *      const geom: ExampleGeometry = (this._geometryPool.pop() || new ExampleGeometry(
     *          // ...your arguments... //)) as ExampleGeometry;
     *
     *      // Put data into geometry's buffer
     *
     *      return geom;
     * }
     */
    build() {
        const geom = (this._geometryPool.pop() || new BatchGeometry(this._attribRedirects, true, this._texIDAttrib, this._texturesPerObject, this._inBatchIDAttrib, this._uniformIDAttrib));
        // We don't really have to remove the buffers because BatchRenderer won't reuse
        // the data in these buffers after the next build() call.
        geom.attribBuffer.update(this._targetCompositeAttributeBuffer.float32View);
        geom.indexBuffer.update(this._targetCompositeIndexBuffer);
        return geom;
    }
    /**
     * @param {PIXI.Geometry} geom - releases back the geometry to be reused. It is expected
     *  that it is not used externally again.
     * @override
     */
    release(geom) {
        this._geometryPool.push(geom);
    }
    /**
     * This lazy getter returns the geometry-merger function. This function
     * takes one argument - the display-object to be appended to the batch -
     * and pushes its geometry to the batch geometry.
     *
     * You can overwrite this property with a custom geometry-merger function
     * if customizing `PIXI.brend.BatchGeometryFactory`.
     *
     * @member {PIXI.brend#IGeometryMerger}
     */
    get geometryMerger() {
        if (!this._geometryMerger) {
            // eslint-disable-next-line @typescript-eslint/no-use-before-define
            this._geometryMerger = new GeometryMergerFactory(this).compile();
        }
        return this._geometryMerger;
    }
    // eslint-disable-next-line require-jsdoc
    set geometryMerger(func) {
        this._geometryMerger = func;
    }
    /**
     * Allocates an attribute buffer with sufficient capacity to hold `size` elements.
     *
     * @param {number} size
     * @protected
     */
    getAttributeBuffer(size) {
        // 8 vertices is enough for 2 quads
        const roundedP2 = utils.nextPow2(Math.ceil(size / 8));
        const roundedSizeIndex = utils.log2(roundedP2);
        const roundedSize = roundedP2 * 8;
        if (this._aBuffers.length <= roundedSizeIndex) {
            this._aBuffers.length = roundedSizeIndex + 1;
        }
        let buffer = this._aBuffers[roundedSizeIndex];
        if (!buffer) {
            this._aBuffers[roundedSize] = buffer = new ViewableBuffer(roundedSize * this._vertexSize);
        }
        return buffer;
    }
    /**
     * Allocates an index buffer (`Uint16Array`) with sufficient capacity to hold `size` indices.
     *
     * @param size
     * @protected
     */
    getIndexBuffer(size) {
        // 12 indices is enough for 2 quads
        const roundedP2 = utils.nextPow2(Math.ceil(size / 12));
        const roundedSizeIndex = utils.log2(roundedP2);
        const roundedSize = roundedP2 * 12;
        if (this._iBuffers.length <= roundedSizeIndex) {
            this._iBuffers.length = roundedSizeIndex + 1;
        }
        let buffer = this._iBuffers[roundedSizeIndex];
        if (!buffer) {
            this._iBuffers[roundedSizeIndex] = buffer = new Uint16Array(roundedSize);
        }
        return buffer;
    }
}
// GeometryMergerFactory uses these variable names.
const CompilerConstants = {
    INDICES_OFFSET: '__offset_indices_',
    FUNC_SOURCE_BUFFER: 'getSourceBuffer',
    // Argument names for the geometryMerger() function.
    packerArguments: [
        'targetObject',
        'factory',
    ],
};
// This was intended to be an inner class of BatchGeometryFactory; however, due to
// a bug in JSDoc, it was placed outside.
// https://github.com/jsdoc/jsdoc/issues/1673
// Factory for generating a geometry-merger function (which appends the geometry of
// a display-object to the batch geometry).
const GeometryMergerFactory = class {
    // We need the BatchGeometryFactory for attribute redirect information.
    constructor(packer) {
        this.packer = packer;
    }
    compile() {
        const packer = this.packer;
        // The function's body/code is placed here.
        let packerBody = ``;
        // Define __offset_${i}, the offset of each attribute in the display-object's
        // geometry, __buffer_${i} the source buffer of the attribute data.
        packer._attribRedirects.forEach((redirect, i) => {
            packerBody += `
                let __offset_${i} = 0;
                const __buffer_${i} = (
                    ${this._compileSourceBufferExpression(redirect, i)});
            `;
        });
        // This loops through each vertex in the display-object's geometry and appends
        // them (attributes are interleaved, so each attribute element is pushed per vertex)
        packerBody += `
            const compositeAttributes = factory._targetCompositeAttributeBuffer;
            const compositeIndices = factory._targetCompositeIndexBuffer;
            let aIndex = factory._aIndex;
            let iIndex = factory._iIndex;
            const textureId = factory._texID;
            const attributeRedirects = factory.attributeRedirects;

            const {
                int8View,
                uint8View,
                int16View,
                uint16View,
                int32View,
                uint32View,
                float32View,
            } = compositeAttributes;

            const vertexCount = ${this._compileVertexCountExpression()};

            let adjustedAIndex = 0;

            for (let vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++)
            {
        `;
        // Eliminate offset conversion when adjacent attributes
        // have similar source-types.
        let skipByteIndexConversion = false;
        // Appends a vertice's attributes (inside the for-loop above).
        for (let i = 0; i < packer._attribRedirects.length; i++) {
            const redirect = packer._attribRedirects[i];
            // Initialize adjustedAIndex in terms of source type.
            if (!skipByteIndexConversion) {
                packerBody += `
        adjustedAIndex = aIndex / ${this._sizeOf(i)};
                `;
            }
            if (typeof redirect.size === 'number') {
                for (let j = 0; j < redirect.size; j++) {
                    packerBody += `
        ${redirect.type}View[adjustedAIndex++] = __buffer_${i}[__offset_${i}++];
                    `;
                }
            }
            else {
                packerBody += `
        ${redirect.type}View[adjustedAIndex++] = __buffer_${i};
                `;
            }
            if (packer._attribRedirects[i + 1] && (this._sizeOf(i + 1) !== this._sizeOf(i))) {
                packerBody += `
        aIndex = adjustedAIndex * ${this._sizeOf(i)};
                `;
            }
            else {
                skipByteIndexConversion = true;
            }
        }
        if (skipByteIndexConversion) {
            if (this._sizeOf(packer._attribRedirects.length - 1) !== 4) {
                packerBody += `
        aIndex = adjustedAIndex * ${this._sizeOf(packer._attribRedirects.length - 1)}
                `;
                skipByteIndexConversion = false;
            }
        }
        if (packer._texturesPerObject > 0) {
            if (packer._texturesPerObject > 1) {
                if (!skipByteIndexConversion) {
                    packerBody += `
        adjustedAIndex = aIndex / 4;
                    `;
                }
                for (let k = 0; k < packer._texturesPerObject; k++) {
                    packerBody += `
        float32View[adjustedAIndex++] = textureId[${k}];
                    `;
                }
                packerBody += `
        aIndex = adjustedAIndex * 4;
                `;
            }
            else if (!skipByteIndexConversion) {
                packerBody += `
        float32View[aIndex / 4] = textureId;
                `;
            }
            else {
                packerBody += `
        float32View[adjustedAIndex++] = textureId;
        aIndex = adjustedAIndex * 4;
                `;
            }
        }
        if (packer._inBatchIDAttrib) {
            packerBody += `
                float32View[adjustedAIndex++] = factory._inBatchID;
                aIndex = adjustedAIndex * 4;
            `;
        }
        if (packer._uniformIDAttrib) {
            packerBody += `
                float32View[adjustedAIndex++] = factory._uniformID;
                aIndex = adjustedAIndex * 4;
            `;
        }
        /* Close the packing for-loop. */
        packerBody += `}
            ${this.packer._indexProperty
            ? `const oldAIndex = this._aIndex;`
            : ''}
            this._aIndex = aIndex;
        `;
        if (this.packer._indexProperty) {
            packerBody += `
    const verticesBefore = oldAIndex / ${this.packer._vertexSize}
    const indexCount  = targetObject['${this.packer._indexProperty}'].length;

    for (let j = 0; j < indexCount; j++)
    {
        compositeIndices[iIndex++] = verticesBefore + targetObject['${this.packer._indexProperty}'][j];
    }

    this._iIndex = iIndex;
            `;
        }
        // eslint-disable-next-line no-new-func
        return new Function(...CompilerConstants.packerArguments, packerBody);
    }
    // Returns an expression that fetches the attribute data source from
    // targetObject (DisplayObject).
    _compileSourceBufferExpression(redirect, i) {
        return (typeof redirect.source === 'string')
            ? `targetObject['${redirect.source}']`
            : `attributeRedirects[${i}].source(targetObject)`;
    }
    _compileVertexCountExpression() {
        if (!this.packer._vertexCountProperty) {
            // auto-calculate based on primary attribute
            return `__buffer_0.length / ${this.packer._attribRedirects[0].size}`;
        }
        return ((typeof this.packer._vertexCountProperty === 'string')
            ? `targetObject.${this.packer._vertexCountProperty}`
            : `${this.packer._vertexCountProperty}`);
    }
    _sizeOf(i) {
        return ViewableBuffer.sizeOf(this.packer._attribRedirects[i].type);
    }
};

function resolveConstantOrProperty(targetObject, property) {
    return (typeof property === 'string')
        ? targetObject[property]
        : property;
}

function resolveFunctionOrProperty(targetObject, property) {
    return (typeof property === 'string')
        ? targetObject[property]
        : property(targetObject);
}

/**
 * Executes the final stage of batch rendering - drawing. The drawer can assume that
 * all display-objects have been into the batch-factory and the batch-geometry factory.
 *
 * @memberof PIXI.brend
 * @class
 */
class BatchDrawer {
    constructor(renderer) {
        /**
         * @member {PIXI.brend.BatchRenderer}
         */
        this.renderer = renderer;
    }
    /**
     * This method will be called after all display-object have been fed into the
     * batch and batch-geometry factories.
     *
     * **Hint**: You will call some form of `BatchGeometryFactory#build`; be sure to release
     * that geometry for reuse in next render pass via `BatchGeometryFactory#release(geom)`.
     */
    draw() {
        const { renderer, _batchFactory: batchFactory, _geometryFactory: geometryFactory, _indexProperty: indexProperty, } = this.renderer;
        const batchList = batchFactory.access();
        const batchCount = batchFactory.size();
        const geom = geometryFactory.build();
        const { gl } = renderer;
        // PixiJS bugs - the shader can't be bound before uploading because uniform sync caching
        // and geometry requires the shader to be bound.
        batchList[0].upload(renderer);
        renderer.shader.bind(this.renderer._shader, false);
        renderer.geometry.bind(geom);
        for (let i = 0; i < batchCount; i++) {
            const batch = batchList[i];
            batch.upload(renderer);
            renderer.shader.bind(this.renderer._shader, false);
            if (indexProperty) {
                // TODO: Get rid of the $indexCount black magic!
                gl.drawElements(gl.TRIANGLES, batch.$indexCount, gl.UNSIGNED_SHORT, batch.geometryOffset * 2);
            }
            else {
                // TODO: Get rid of the $vertexCount black magic!
                gl.drawArrays(gl.TRIANGLES, batch.geometryOffset, batch.$vertexCount);
            }
            batch.reset();
        }
        geometryFactory.release(geom);
    }
}

/**
 * This object renderer renders multiple display-objects in batches. It can greatly
 * reduce the number of draw calls issued per frame.
 *
 * ## Batch Rendering Pipeline
 *
 * The batch rendering pipeline consists of the following stages:
 *
 * * **Display-Object Buffering**: Each display-object is kept in a buffer until it fills up or a
 * flush is required.
 *
 * * **Batch Generation**: In a sliding window, display-object batches are generated based off of certain
 * constraints like GPU texture units and the uniforms used in each display-object. This is done using an
 * instance of {@link PIXI.brend.BatchFactory}.
 *
 * * **Geometry Composition**: The geometries of all display-objects are merged together in a
 * composite geometry. This is done using an instance of {@link PIXI.brend.BatchGeometryFactory}.
 *
 * * **Drawing**: Each batch is rendered in-order using `gl.draw*`. The textures and
 * uniforms of each display-object are uploaded as arrays. This is done using an instance of
 * {@link PIXI.brend.BatchDrawer}.
 *
 * Each stage in this pipeline can be configured by overriding the appropriate component and passing that
 * class to `BatchRendererPluginFactory.from*`.
 *
 * ## Shaders
 *
 * ### Shader templates
 *
 * Since the max. display-object count per batch is not known until the WebGL context is created,
 * shaders are generated at runtime by processing shader templates. A shader templates has "%macros%"
 * that are replaced by constants at runtime.
 *
 * To use shader templates, simply use {@link PIXI.brend.BatchShaderFactory#derive}. This will generate a
 * function that derives a shader from your template at runtime.
 *
 * ### Textures
 *
 * The batch renderer uploads textures in the `uniform sampler2D uSamplers[%texturesPerBatch%];`. The
 * `varying float vTextureId` defines the index into this array that holds the current display-object's
 * texture.
 *
 * ### Uniforms
 *
 * This renderer currently does not support customized uniforms for display-objects. This is a
 * work-in-progress feature.
 *
 * ## Learn more
 * This batch renderer uses the PixiJS object-renderer API to hook itself:
 *
 * 1. [PIXI.ObjectRenderer]{@link http://pixijs.download/release/docs/PIXI.ObjectRenderer.html}
 *
 * 2. [PIXI.AbstractBatchRenderer]{@link http://pixijs.download/release/docs/PIXI.AbstractBatchRenderer.html}
 *
 * @memberof PIXI.brend
 * @class
 * @extends PIXI.ObjectRenderer
 * @example
 * import * as PIXI from 'pixi.js';
 * import { BatchRendererPluginFactory } from 'pixi-batch-renderer';
 *
 * // Define the geometry of your display-object and create a BatchRenderer using
 * // BatchRendererPluginFactory. Register it as a plugin with PIXI.Renderer.
 * PIXI.Renderer.registerPlugin('ExampleBatchRenderer', BatchRendererPluginFactory.from(...));
 *
 * class ExampleObject extends PIXI.Container
 * {
 *     _render(renderer: PIXI.Renderer): void
 *     {
 *          // BatchRenderer will handle the whole rendering process for you!
 *          renderer.batch.setObjectRenderer(renderer.plugins['ExampleBatchRenderer']);
 *          renderer.plugins['ExampleBatchRenderer'].render(this);
 *     }
 * }
 */
class BatchRenderer extends ObjectRenderer {
    /**
     * Creates a batch renderer the renders display-objects with the described geometry.
     *
     * To register a batch-renderer plugin, you must use the API provided by `PIXI.brend.BatchRendererPluginFactory`.
     *
     * @param {PIXI.Renderer} renderer - renderer to attach to
     * @param {object} options
     * @param {PIXI.brend.AttributeRedirect[]} options.attribSet
     * @param {string | null} options.indexProperty
     * @param {string | number} [options.vertexCountProperty]
     * @param {string | null} options.textureProperty
     * @param {number} [options.texturesPerObject=1]
     * @param {string} options.texIDAttrib - name of texture-id attribute variable
     * @param {Function} options.stateFunction - returns a PIXI.State for an object
     * @param {Function} options.shaderFunction - generates a shader given this instance
     * @param {Class} [options.BatchGeometryFactory=PIXI.brend.BatchGeometry]
     * @param {Class} [options.BatchFactoryClass=PIXI.brend.StdBatchFactory]
     * @param {Class} [options.BatchDrawer=PIXI.brend.BatchDrawer]
     * @see PIXI.brend.BatchShaderFactory
     * @see PIXI.brend.StdBatchFactory
     * @see PIXI.brend.BatchGeometryFactory
     * @see PIXI.brend.BatchDrawer
     */
    constructor(renderer, options) {
        super(renderer);
        /**
         * Attribute redirects
         * @member {PIXI.brend.AttributeRedirect[]}
         * @protected
         * @readonly
         */
        this._attribRedirects = options.attribSet;
        /**
         * Indices property
         * @member {string}
         * @protected
         * @readonly
         */
        this._indexProperty = options.indexProperty;
        /**
         * Vertex count property (optional)
         * @member {string}
         * @protected
         * @readonly
         */
        this._vertexCountProperty = options.vertexCountProperty;
        /**
         * Texture(s) property
         * @member {string}
         * @protected
         * @readonly
         */
        this._textureProperty = options.textureProperty;
        /**
         * Textures per display-object
         * @member {number}
         * @protected
         * @readonly
         * @default 1
         */
        this._texturesPerObject = typeof options.texturesPerObject !== 'undefined' ? options.texturesPerObject : 1;
        /**
         * Texture ID attribute
         * @member {string}
         * @protected
         * @readonly
         */
        this._texIDAttrib = options.texIDAttrib;
        /**
         * Indexes the display-object in the batch.
         * @member {string}
         * @protected
         * @readonly
         */
        this._inBatchIDAttrib = options.inBatchIDAttrib;
        /**
         * State generating function (takes a display-object)
         * @member {Function}
         * @default () => PIXI.State.for2d()
         * @protected
         * @readonly
         */
        this._stateFunction = options.stateFunction || (() => State.for2d());
        /**
         * Shader generating function (takes the batch renderer)
         * @member {Function}
         * @protected
         * @see PIXI.brend.BatchShaderFactory
         * @readonly
         */
        this._shaderFunction = options.shaderFunction;
        /**
         * Batch-factory class.
         * @member {Class}
         * @protected
         * @default PIXI.brend.StdBatchFactory
         * @readonly
         */
        this._BatchFactoryClass = options.BatchFactoryClass || StdBatchFactory;
        /**
         * Batch-geometry factory class. Its constructor takes one argument - this batch renderer.
         * @member {Class}
         * @protected
         * @default PIXI.brend.BatchGeometryFactory
         * @readonly
         */
        this._BatchGeometryFactoryClass = options.BatchGeometryFactoryClass || BatchGeometryFactory;
        /**
         * Batch drawer class. Its constructor takes one argument - this batch renderer.
         * @member {Class}
         * @protected
         * @default PIXI.brend.BatchDrawer
         * @readonly
         */
        this._BatchDrawerClass = options.BatchDrawerClass || BatchDrawer;
        /**
         * Uniform redirects. If you use uniforms in your shader, be sure to use one the compatible
         * batch factories (like `PIXI.brend.AggregateUniformsBatchFactory`).
         * @member {PIXI.brend.UniformRedirect[]}
         * @protected
         * @default null
         * @readonly
         */
        this._uniformRedirects = options.uniformSet || null;
        /**
         * Indexes the uniforms of the display-object in the uniform arrays. This is not equal to the
         * in-batch ID because equal uniforms are not uploaded twice.
         * @member {string}
         * @protected
         * @readonly
         */
        this._uniformIDAttrib = options.uniformIDAttrib;
        /**
         * The options used to create this batch renderer.
         * @readonly {object}
         * @protected
         * @readonly
         */
        this.options = options;
        // Although the runners property is not a public API, it is required to
        // handle contextChange events.
        this.renderer.runners.contextChange.add(this);
        // If the WebGL context has already been created, initialization requires a
        // syntheic call to contextChange.
        if (this.renderer.gl) {
            this.contextChange();
        }
        this._objectBuffer = [];
        this._bufferedVertices = 0;
        this._bufferedIndices = 0;
        this._shader = null;
    }
    /**
     * Internal method that is called whenever the renderer's WebGL context changes.
     */
    contextChange() {
        const gl = this.renderer.gl;
        if (settings.PREFER_ENV === ENV.WEBGL_LEGACY) {
            this.MAX_TEXTURES = 1;
        }
        else {
            this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), settings.SPRITE_MAX_TEXTURES);
        }
        /**
         * @member {_BatchFactoryClass}
         * @readonly
         * @protected
         */
        this._batchFactory = new this._BatchFactoryClass(this);
        /**
         * @member {_BatchGeometryFactoryClass}
         * @readonly
         * @protected
         */
        this._geometryFactory = new this._BatchGeometryFactoryClass(this);
        /**
         * @member {_BatchDrawerClass}
         * @readonly
         * @protected
         */
        this._drawer = new this._BatchDrawerClass(this);
    }
    /**
     * This is an internal method. It ensures that the batch renderer is ready to start buffering display-objects.
     * This is automatically invoked by the renderer's batch system.
     *
     * @override
     */
    start() {
        this._objectBuffer.length = 0;
        this._bufferedVertices = 0;
        this._bufferedIndices = 0;
        this._shader = this._shaderFunction(this);
    }
    /**
     * Adds the display-object to be rendered in a batch. Your display-object's render/_render method should call
     * this as follows:
     *
     * ```js
     * renderer.setObjectRenderer(<BatchRenderer>);
     * <BatchRenderer>.render(this);
     * ```
     *
     * @param {PIXI.DisplayObject} displayObject
     * @override
     */
    render(displayObject) {
        this._objectBuffer.push(displayObject);
        this._bufferedVertices += this._vertexCountFor(displayObject);
        if (this._indexProperty) {
            this._bufferedIndices += resolveConstantOrProperty(displayObject, this._indexProperty).length;
        }
    }
    /**
     * Forces buffered display-objects to be rendered immediately. This should not be called unless absolutely
     * necessary like the following scenarios:
     *
     * * before directly rendering your display-object, to preserve render-order.
     *
     * * to do a nested render pass (calling `Renderer#render` inside a `render` method)
     *   because the PixiJS renderer is not re-entrant.
     *
     * @override
     */
    flush() {
        const { _batchFactory: batchFactory, _geometryFactory: geometryFactory, _stateFunction: stateFunction } = this;
        const buffer = this._objectBuffer;
        const bufferLength = buffer.length;
        // Reset components
        batchFactory.reset();
        geometryFactory.init(this._bufferedVertices, this._bufferedIndices);
        let batchStart = 0;
        // Loop through display-objects and create batches
        for (let objectIndex = 0; objectIndex < bufferLength;) {
            const target = buffer[objectIndex];
            const wasPut = batchFactory.put(target, resolveFunctionOrProperty(target, stateFunction));
            if (!wasPut) {
                batchFactory.build(batchStart);
                batchStart = objectIndex;
            }
            else {
                ++objectIndex;
            }
        }
        // Generate the last batch, if required.
        if (!batchFactory.ready()) {
            batchFactory.build(batchStart);
        }
        const batchList = batchFactory.access();
        const batchCount = batchFactory.size();
        let indices = 0;
        // Loop through batches and their display-object list to compose geometry
        for (let i = 0; i < batchCount; i++) // loop-per(batch)
         {
            const batch = batchList[i];
            const batchBuffer = batch.batchBuffer;
            const batchLength = batchBuffer.length;
            let vertexCount = 0;
            let indexCount = 0;
            for (let j = 0; j < batchLength; j++) // loop-per(targetObject)
             {
                const targetObject = batchBuffer[j];
                if (this._indexProperty) {
                    indexCount += resolveConstantOrProperty(targetObject, this._indexProperty).length;
                }
                else {
                    vertexCount += resolveConstantOrProperty(targetObject, this._vertexCountProperty);
                }
                // externally-defined properties for draw calls
                batch.$vertexCount = vertexCount;
                batch.$indexCount = indexCount;
                batch.geometryOffset = indices;
                indices += batch.$indexCount;
                geometryFactory.append(targetObject, batch);
            }
        }
        // BatchDrawer handles the rest!
        this._drawer.draw();
    }
    /**
     * Internal method that stops buffering of display-objects and flushes any existing buffers.
     *
     * @override
     */
    stop() {
        if (this._bufferedVertices) {
            this.flush();
        }
    }
    // Should we document this?
    _vertexCountFor(targetObject) {
        return (this._vertexCountProperty)
            ? resolveConstantOrProperty(targetObject, this._vertexCountProperty)
            : resolveFunctionOrProperty(targetObject, this._attribRedirects[0].source).length
                / this._attribRedirects[0].size;
    }
}

/**
 * Factory class for creating a batch-renderer.
 *
 * @memberof PIXI.brend
 * @class
 * @example
 *  import * as PIXI from 'pixi.js';
 *  import { AttributeRedirect, BatchShaderFactory, BatchRendererPluginFactory } from 'pixi-batch-renderer';
 *
 *  // Define the geometry of Sprite.
 *  const attribSet = [
 *      // Sprite vertexData contains global coordinates of the corners
 *      new AttributeRedirect({
 *          source: 'vertexData',
 *          attrib: 'aVertex',
 *          type: 'float32',
 *          size: 2,
 *          glType: PIXI.TYPES.FLOAT,
 *          glSize: 2,
 *      }),
 *      // Sprite uvs contains the normalized texture coordinates for each corner/vertex
 *      new AttributeRedirect({
 *          source: 'uvs',
 *          attrib: 'aTextureCoord',
 *          type: 'float32',
 *          size: 2,
 *          glType: PIXI.TYPES.FLOAT,
 *          glSize: 2,
 *      }),
 *  ];
 *
 *  const shaderFunction = new BatchShaderFactory(// 1. vertexShader
 *  `
 *  attribute vec2 aVertex;
 *  attribute vec2 aTextureCoord;
 *  attribute float aTextureId;
 *
 *  varying float vTextureId;
 *  varying vec2 vTextureCoord;
 *
 *  uniform mat3 projectionMatrix;
 *
 *  void main() {
 *      gl_Position = vec4((projectionMatrix * vec3(aVertex, 1)).xy, 0, 1);
 *      vTextureId = aTextureId;
 *      vTextureCoord = aTextureCoord;
 *  }
 *  `,
 *  `
 *  uniform sampler2D uSamplers[%texturesPerBatch%];
 *  varying float vTextureId;
 *  varying vec2 vTextureCoord;
 *
 *  void main(void){
 *      vec4 color;
 *
 *      // get color, which is the pixel in texture uSamplers[vTextureId] at vTextureCoord
 *      for (int k = 0; k < %texturesPerBatch%; ++k) {
 *          if (int(vTextureId) == k) {
 *              color = texture2D(uSamplers[k], vTextureCoord);
 *              break;
 *          }
 *      }
 *
 *      gl_FragColor = color;
 *  }
 *  `,
 *  {// we don't use any uniforms except uSamplers, which is handled by default!
 *  },
 *  // no custom template injectors
 *  // disable vertex shader macros by default
 *  ).derive();
 *
 *  // Produce the SpriteBatchRenderer class!
 *  const SpriteBatchRenderer = BatchRendererPluginFactory.from({
 *      attribSet,
 *      indexProperty: 'indices',
 *      textureProperty: 'texture',
 *      texturesPerObject: 1, // default
 *      texIDAttrib: 'aTextureId',
 *      stateFunction: () => PIXI.State.for2d(), // default
 *      shaderFunction
 *  });
 *
 *  PIXI.Renderer.registerPlugin('customBatch', SpriteBatchRenderer);
 *
 *  // Sprite will render using SpriteBatchRenderer instead of default PixiJS
 *  // batch renderer. Instead of targetting PIXI.Sprite, you can write a batch
 *  // renderer for a custom display-object too! (See main page for that example!)
 *  const exampleSprite = PIXI.Sprite.from('./asset/example.png');
 *  exampleSprite.pluginName = 'customBatch';
 *  exampleSprite.width = 128;
 *  exampleSprite.height = 128;
 */
class BatchRendererPluginFactory {
    /**
     * Generates a fully customized `BatchRenderer` that aggregates primitives and textures. This is useful
     * for non-uniform based display-objects.
     *
     * @param {object} options
     * @param {PIXI.brend.AttributeRedirect[]} options.attribSet - set of geometry attributes
     * @param {string | Array<number>} options.indexProperty - no. of indices on display-object
     * @param {string | number} options.vertexCountProperty - no. of vertices on display-object
     * @param {string} options.textureProperty - textures used in display-object
     * @param {number} options.texturePerObject - no. of textures used per display-object
     * @param {string} options.texIDAttrib - used to find texture for each display-object (index into array)
     * @param {string | Function}[options.stateFunction= ()=>PIXI.State.for2d()] - callback that finds the WebGL
     *  state required for display-object shader
     * @param {Function} options.shaderFunction - shader generator function
     * @param {Class}[options.BatchGeometryFactoryClass] - custom batch geometry factory class
     * @param {Class} [options.BatchFactoryClass] - custom batch factory class
     * @param {Class} [options.BatchRendererClass] - custom batch renderer class
     * @param {Class} [options.BatchDrawerClass] - custom batch drawer class
     * @static
     */
    static from(options) {
        // This class wraps around BatchRendererClass's constructor and passes the options from the outer scope.
        return class extends (options.BatchRendererClass || BatchRenderer) {
            constructor(renderer) {
                super(renderer, options);
            }
        };
    }
}

// This file might need a cleanup :)
// JavaScript is stupid enough not to have a replaceAll in String. This is a temporary
// solution and we should depend on an actually polyfill.
function _replaceAll(target, search, replacement) {
    return target.replace(new RegExp(search, 'g'), replacement);
}
function injectTexturesPerBatch(batchRenderer) {
    return `${batchRenderer.MAX_TEXTURES}`;
}
const INJECTORS = {
    uniformsPerBatch(renderer) {
        return `${renderer._batchFactory.MAX_UNIFORMS}`;
    },
};
/**
 * Exposes an easy-to-use API for generating shader-functions to use in
 * the batch renderer!
 *
 * You are required to provide an injector map, which maps macros to functions
 * that return a string value for those macros given a renderer. By default, only one
 * injector is used - the textures per batch `%texturesPerBatch%` macro. This is replaced by
 * the number of textures passed to the `uSamplers` textures uniform.
 *
 * **Built-in Injectors**:
 *
 * * `%texturesPerBatch%`: replaced by the max. textures allowed by WebGL context
 *
 * * `%uniformsPerBatch%`: replaced by the (aggregate-uniforms) batch factory's `MAX_UNIFORMS` property.
 *
 * @memberof PIXI.brend
 * @class
 */
class BatchShaderFactory {
    /**
     * WARNING: Do not pass `uSamplers` in your uniforms. They
     *  will be added to your shader instance directly.
     *
     * @param {string} vertexShaderTemplate
     * @param {string} fragmentShaderTemplate
     * @param {UniformGroup | Map<string, object>} uniforms
     * @param {Object.<String, PIXI.brend.InjectorFunction>} [templateInjectors]
     * @param {boolean} [disableVertexShaderTemplate=true] - turn off (true)
     *      if you aren't using macros in the vertex shader
     */
    constructor(vertexShaderTemplate, fragmentShaderTemplate, uniforms = {}, templateInjectors = {}, disableVertexShaderTemplate = true) {
        if (!templateInjectors['%texturesPerBatch%']) {
            templateInjectors['%texturesPerBatch%'] = injectTexturesPerBatch;
        }
        if (!templateInjectors['%uniformsPerBatch%']) {
            templateInjectors['%uniformsPerBatch%'] = INJECTORS.uniformsPerBatch;
        }
        this._vertexShaderTemplate = vertexShaderTemplate;
        this._fragmentShaderTemplate = fragmentShaderTemplate;
        this._uniforms = uniforms;
        this._templateInjectors = templateInjectors;
        /**
         * Disable vertex shader templates to speed up shader
         * generation.
         *
         * @member {Boolean}
         */
        this.disableVertexShaderTemplate = disableVertexShaderTemplate;
        /**
         * Maps the stringifed state of the batch renderer to the
         * generated shader.
         *
         * @private
         * @member {Object.<String, PIXI.Shader>}
         */
        this._cache = {};
        /**
         * Unstringifed current state of the batch renderer.
         *
         * @private
         * @member {Object.<String, String>}
         * @see {PIXI.brend.ShaderGenerator#_generateInjectorBasedState}
         */
        this._cState = null;
    }
    /**
     * This essentially returns a function for generating the shader for a batch
     * renderer.
     *
     * @return shader function that can be given to the batch renderer
     */
    derive() {
        return (batchRenderer) => {
            const stringState = this._generateInjectorBasedState(batchRenderer);
            const cachedShader = this._cache[stringState];
            if (cachedShader) {
                return cachedShader;
            }
            return this._generateShader(stringState, batchRenderer);
        };
    }
    _generateInjectorBasedState(batchRenderer) {
        let state = '';
        const cState = this._cState = {};
        for (const injectorMacro in this._templateInjectors) {
            const val = this._templateInjectors[injectorMacro](batchRenderer);
            state += val;
            cState[injectorMacro] = val;
        }
        return state;
    }
    _generateShader(stringState, renderer) {
        let vertexShaderTemplate = this._vertexShaderTemplate.slice(0);
        let fragmentShaderTemplate = this._fragmentShaderTemplate.slice(0);
        for (const injectorTemplate in this._cState) {
            if (!this.disableVertexShaderTemplate) {
                vertexShaderTemplate = _replaceAll(vertexShaderTemplate, injectorTemplate, this._cState[injectorTemplate]);
            }
            fragmentShaderTemplate = _replaceAll(fragmentShaderTemplate, injectorTemplate, this._cState[injectorTemplate]);
        }
        const shader = Shader.from(vertexShaderTemplate, fragmentShaderTemplate, this._uniforms);
        const textures = new Array(renderer.MAX_TEXTURES);
        for (let i = 0; i < textures.length; i++) {
            textures[i] = i;
        }
        shader.uniforms.uSamplers = textures;
        this._cache[stringState] = shader;
        return shader;
    }
}

/**
 * Allows usage of uniforms when rendering display-objects in batches. It expects you to
 * aggregate each display-object's uniforms in an array and that the shader will pick
 * the appropriate uniform at runtime (an index into the uniforms array will be passed).
 *
 * **Usage in shader:**
 * ```
 * // Your display-objects' affine transforms are aggregated into this array.
 * uniform mat3d affineTransform[];
 *
 * // For WebGL1+ machines, your uniforms may be fetched by the uniform-ID attrib (float).
 * varying float vUniformID;
 *
 * // For WebGL-2 only, to prevent interpolation overhead, you may use the flat in variables. You
 * // can configure this in AggregateUniformShaderFactory.
 * flat in int uniformID;
 * ```
 *
 * # No Aggregation Mode
 *
 * Aggregating uniforms into arrays requries a uniform-ID attribute to be uploaded as well. This
 * may cost a lot of memory if your uniforms don't really change a lot. For these cases, you can
 * disable uniform aggregation by not passing a `uniformIDAttrib`. This will make batches **only**
 * have one value for each uniform. The uniforms will still be uploaded as 1-element arrays, however.
 *
 * @memberof PIXI.brend
 * @class
 * @extends PIXI.brend.StdBatch
 */
class AggregateUniformsBatch extends StdBatch {
    constructor(renderer, geometryOffset) {
        super(geometryOffset);
        /**
         * Renderer holding the uniform redirects
         * @member {PIXI.brend.BatchRenderer}
         */
        this.renderer = renderer;
        /**
         * The buffer of uniform arrays of the display-objects
         * @member {Object<string, Array<UniformGroup>>}
         */
        this.uniformBuffer = null;
        /**
         * Array mapping the in-batch ID to the uniform ID.
         * @member {Array<number>}
         */
        this.uniformMap = null;
        /**
         * No. of uniforms buffered (per uniform name)
         * @member {number}
         */
        this.uniformLength = 0;
    }
    /**
     * @param {PIXI.Renderer} renderer
     * @override
     */
    upload(renderer) {
        super.upload(renderer);
        const { _uniformRedirects: uniformRedirects, _shader: shader } = this.renderer;
        for (let i = 0, j = uniformRedirects.length; i < j; i++) {
            const glslIdentifer = uniformRedirects[i].glslIdentifer;
            shader.uniforms[glslIdentifer] = this.uniformBuffer[glslIdentifer];
        }
        //        shader.uniformGroup.update();
    }
    /**
     * @override
     */
    reset() {
        super.reset();
        for (const uniformName in this.uniformBuffer) {
            this.uniformBuffer[uniformName].length = 0;
        }
    }
}

/**
 * Factory for producing aggregate-uniforms batches. This is useful for shaders that
 * **must** use uniforms.
 *
 * @memberof PIXI.brend.AggregateUniformsBatchFactory
 */
class AggregateUniformsBatchFactory extends StdBatchFactory {
    constructor(renderer) {
        super(renderer);
        /**
         * The max. uniforms until the batch is filled
         * @member {number}
         * @readonly
         */
        // Max. no. of uniforms that can be passed to the batch shader. We divide by four because
        // mat4d/vec4 count as four uniforms.
        this.MAX_UNIFORMS = Math.floor(Math.min(renderer.renderer.gl.getParameter(renderer.renderer.gl.MAX_VERTEX_UNIFORM_VECTORS), renderer.renderer.gl.getParameter(renderer.renderer.gl.MAX_FRAGMENT_UNIFORM_VECTORS))
            / (4 * renderer._uniformRedirects.length));
        this.uniformBuffer = this._createUniformBuffer();
        this.uniformMap = [];
        this.uniformLength = 0;
    }
    /**
     * @returns {AggregateUniformsBatch}
     */
    _newBatch() {
        const batch = new AggregateUniformsBatch(this._renderer);
        // All pooled batches will have a buffer
        batch.uniformBuffer = this._createUniformBuffer();
        batch.uniformMap = [];
        return batch;
    }
    /**
     * Stores uniforms in the current batch, if possible.
     *
     * If you want to override this, be sure to return beforehand if `super._put` returns
     * false:
     * ```
     * _put(displayObject: PIXI.DisplayObject): boolean
     * {
     *      if (!super._put(displayObject))
     *      {
     *          return false;
     *      }
     *
     *      // Your logic ...
     * }
     * ```
     *
     * @protected
     * @param {PIXI.DisplayObject} displayObject
     * @returns {boolean} - whether uniforms can be buffered
     */
    _put(displayObject) {
        if (!this._renderer._uniformIDAttrib) {
            // No aggregation mode! If uniforms already buffered, they **must** match or batch will break.
            if (this.uniformLength > 0) {
                const id = this._matchUniforms(displayObject);
                if (id > 0) {
                    return true;
                }
                return false;
            }
        }
        if (this.uniformLength + 1 >= this.MAX_UNIFORMS) {
            return false;
        }
        if (this._renderer._uniformIDAttrib) {
            const id = this._matchUniforms(displayObject);
            if (id > 0) {
                this.uniformMap.push(id);
                return true;
            }
        }
        // Push each uniform into the buffer
        for (let i = 0, j = this._renderer._uniformRedirects.length; i < j; i++) {
            const uniformRedirect = this._renderer._uniformRedirects[i];
            const { source, glslIdentifer } = uniformRedirect;
            this.uniformBuffer[glslIdentifer].push(typeof source === 'string'
                ? displayObject[source] : source(displayObject));
        }
        this.uniformMap.push(this.uniformLength);
        ++this.uniformLength;
        return true;
    }
    /**
     * @protected
     * @param {AggregateUniformBatch} batch
     */
    _buildBatch(batch) {
        super._buildBatch(batch);
        const buffer = batch.uniformBuffer;
        const map = batch.uniformMap;
        batch.uniformBuffer = this.uniformBuffer;
        batch.uniformMap = this.uniformMap;
        batch.uniformLength = this.uniformLength;
        // Swap & reset instead of new allocation
        this.uniformBuffer = buffer;
        this.uniformMap = map;
        this.uniformLength = 0;
        this._resetUniformBuffer(this.uniformBuffer);
        this.uniformMap.length = 0;
    }
    /**
     * Creates an array for each uniform-name in an object.
     *
     * @returns - the object created (the uniform buffer)
     */
    _createUniformBuffer() {
        const buffer = {};
        for (let i = 0, j = this._renderer._uniformRedirects.length; i < j; i++) {
            const uniformRedirect = this._renderer._uniformRedirects[i];
            buffer[uniformRedirect.glslIdentifer] = [];
        }
        return buffer;
    }
    /**
     * Resets each array in the uniform buffer
     * @param {object} buffer
     */
    _resetUniformBuffer(buffer) {
        for (let i = 0, j = this._renderer._uniformRedirects.length; i < j; i++) {
            const uniformRedirect = this._renderer._uniformRedirects[i];
            buffer[uniformRedirect.glslIdentifer].length = 0;
        }
    }
    /**
     * Finds a matching set of uniforms in the buffer.
     */
    _matchUniforms(displayObject) {
        const uniforms = this._renderer._uniformRedirects;
        for (let i = this.uniformLength - 1; i >= 0; i--) {
            let isMatch = true;
            for (let k = 0, n = uniforms.length; k < n; k++) {
                const { glslIdentifer, source } = uniforms[k];
                const value = typeof source === 'string'
                    ? displayObject[source]
                    : source(displayObject);
                if (!this._compareUniforms(value, this.uniformBuffer[glslIdentifer][i])) {
                    isMatch = false;
                    break;
                }
            }
            if (isMatch) {
                return i;
            }
        }
        return -1;
    }
    // Compares two uniforms u1 & u2 for equality.
    _compareUniforms(u1, u2) {
        if (u1 === u2) {
            return true;
        }
        // UniformGroups must have referential equality
        if (u1.group || u2.group) {
            return false;
        }
        // Allow equals() method for custom stuff.
        if (u1.equals) {
            return u1.equals(u2);
        }
        // Test one-depth equality for arrays
        if (Array.isArray(u1) && Array.isArray(u2)) {
            if (u1.length !== u2.length) {
                return false;
            }
            for (let i = 0, j = u1.length; i < j; i++) {
                // Referential equality for array elements
                if (u1[i] !== u2[i]) {
                    return false;
                }
            }
            return true;
        }
        if (u1 instanceof Point && u2 instanceof Point) {
            return u1.x === u2.x && u1.y === u2.y;
        }
        if (u1 instanceof Matrix && u2 instanceof Matrix) {
            return u1.a === u2.a && u1.b === u2.b
                && u1.c === u2.c && u1.d === u2.d
                && u1.tx === u2.tx && u1.ty === u2.ty;
        }
        // Unlikely for batch rendering
        if (u1 instanceof BaseTexture && u2 instanceof BaseTexture) {
            return u1.uid === u2.uid;
        }
        return true;
    }
}

/**
 * @memberof PIXI
 * @namespace brend
 * @example
 * // ES6 import
 * import * as brend from 'pixi-batch-renderer';
 * const { BatchRendererPluginFactory } = brend;
 * @example
 * // CommonJS require
 * const brend = require('pixi-batch-renderer');
 * const BatchRendererPluginFactory = brend.BatchRendererPluginFactory;
 */
/**
 * Used by `PIXI.brend.BatchGeometryFactory` to merge the geometry of a
 * display-object into the whole batch's geometry.
 *
 * @memberof PIXI.brend#
 * @function IGeometryMerger
 * @param {PIXI.DisplayObject} displayObject
 * @param {PIXI.brend.BatchGeometryFactory} factory
 * @see PIXI.brend.BatchGeometryFactory#geometryMerger
 */
/**
 * @function
 * @name InjectorFunction
 * @memberof PIXI.brend
 *
 * @param {PIXI.brend.BatchRenderer} batchRenderer
 * @return {string} value of the macro for this renderer
 */

export { AggregateUniformsBatch, AggregateUniformsBatchFactory, AttributeRedirect, StdBatch as Batch, StdBatchFactory as BatchGenerator, BatchRenderer, BatchRendererPluginFactory, BatchShaderFactory, BatchGeometryFactory as GeometryPacker, Redirect, UniformRedirect };
//# sourceMappingURL=pixi-batch-renderer.mjs.map