#

API Docs for: 2.0.0
Show: Inherited Protected Private Deprecated

# File: src/pixi/renderers/webgl/utils/WebGLSpriteBatch.js

    /**
     * @author Mat Groves
     *
     * Big thanks to the very clever Matt DesLauriers <mattdesl> https://github.com/mattdesl/
     * for creating the original pixi version!
     *
     * Heavily inspired by LibGDX's WebGLSpriteBatch:
     * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/WebGLSpriteBatch.java
     */

     /**
     *
     * @class WebGLSpriteBatch
     * @private
     * @constructor
     */
    PIXI.WebGLSpriteBatch = function()
    {
        /**
         * @property vertSize
         * @type Number
         */
        this.vertSize = 6;

        /**
         * The number of images in the SpriteBatch before it flushes
         * @property size
         * @type Number
         */
        this.size = 2000;//Math.pow(2, 16) /  this.vertSize;

        //the total number of floats in our batch
        var numVerts = this.size * 4 *  this.vertSize;
        //the total number of indices in our batch
        var numIndices = this.size * 6;

        /**
        * Holds the vertices
        *
        * @property vertices
        * @type Float32Array
        */
        this.vertices = new Float32Array(numVerts);

        /**
         * Holds the indices
         *
         * @property indices
         * @type Uint16Array
         */
        this.indices = new Uint16Array(numIndices);

        /**
         * @property lastIndexCount
         * @type Number
         */
        this.lastIndexCount = 0;

        for (var i=0, j=0; i < numIndices; i += 6, j += 4)
        {
            this.indices[i + 0] = j + 0;
            this.indices[i + 1] = j + 1;
            this.indices[i + 2] = j + 2;
            this.indices[i + 3] = j + 0;
            this.indices[i + 4] = j + 2;
            this.indices[i + 5] = j + 3;
        }

        /**
         * @property drawing
         * @type Boolean
         */
        this.drawing = false;

        /**
         * @property currentBatchSize
         * @type Number
         */
        this.currentBatchSize = 0;

        /**
         * @property currentBaseTexture
         * @type BaseTexture
         */
        this.currentBaseTexture = null;

        /**
         * @property dirty
         * @type Boolean
         */
        this.dirty = true;

        /**
         * @property textures
         * @type Array
         */
        this.textures = [];

        /**
         * @property blendModes
         * @type Array
         */
        this.blendModes = [];

        /**
         * @property shaders
         * @type Array
         */
        this.shaders = [];

        /**
         * @property sprites
         * @type Array
         */
        this.sprites = [];

        /**
         * @property defaultShader
         * @type AbstractFilter
         */
        this.defaultShader = new PIXI.AbstractFilter([
            'precision lowp float;',
            'varying vec2 vTextureCoord;',
            'varying vec4 vColor;',
            'uniform sampler2D uSampler;',
            'void main(void) {',
            '   gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;',
            '}'
        ]);
    };

    /**
    * @method setContext
    * @param gl {WebGLContext} the current WebGL drawing context
    */
    PIXI.WebGLSpriteBatch.prototype.setContext = function(gl)
    {
        this.gl = gl;

        // create a couple of buffers
        this.vertexBuffer = gl.createBuffer();
        this.indexBuffer = gl.createBuffer();

        // 65535 is max index, so 65535 / 6 = 10922.

        //upload the index data
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW);

        gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW);

        this.currentBlendMode = 99999;

        var shader = new PIXI.PixiShader(gl);

        shader.fragmentSrc = this.defaultShader.fragmentSrc;
        shader.uniforms = {};
        shader.init();

        this.defaultShader.shaders[gl.id] = shader;
    };

    /**
    * @method begin
    * @param renderSession {Object} The RenderSession object
    */
    PIXI.WebGLSpriteBatch.prototype.begin = function(renderSession)
    {
        this.renderSession = renderSession;
        this.shader = this.renderSession.shaderManager.defaultShader;

        this.start();
    };

    /**
    * @method end
    */
    PIXI.WebGLSpriteBatch.prototype.end = function()
    {
        this.flush();
    };

    /**
    * @method render
    * @param sprite {Sprite} the sprite to render when using this spritebatch
    */
    PIXI.WebGLSpriteBatch.prototype.render = function(sprite)
    {
        var texture = sprite.texture;

       //TODO set blend modes..
        // check texture..
        if(this.currentBatchSize >= this.size)
        {
            this.flush();
            this.currentBaseTexture = texture.baseTexture;
        }

        // get the uvs for the texture
        var uvs = texture._uvs;
        // if the uvs have not updated then no point rendering just yet!
        if(!uvs)return;

        // get the sprites current alpha
        var alpha = sprite.worldAlpha;
        var tint = sprite.tint;

        var verticies = this.vertices;

        // TODO trim??
        var aX = sprite.anchor.x;
        var aY = sprite.anchor.y;

        var w0, w1, h0, h1;

        if (texture.trim)
        {
            // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords..
            var trim = texture.trim;

            w1 = trim.x - aX * trim.width;
            w0 = w1 + texture.crop.width;

            h1 = trim.y - aY * trim.height;
            h0 = h1 + texture.crop.height;

        }
        else
        {
            w0 = (texture.frame.width ) * (1-aX);
            w1 = (texture.frame.width ) * -aX;

            h0 = texture.frame.height * (1-aY);
            h1 = texture.frame.height * -aY;
        }

        var index = this.currentBatchSize * 4 * this.vertSize;

        var resolution = texture.baseTexture.resolution;

        var worldTransform = sprite.worldTransform;

        var a = worldTransform.a / resolution;
        var b = worldTransform.b / resolution;
        var c = worldTransform.c / resolution;
        var d = worldTransform.d / resolution;
        var tx = worldTransform.tx;
        var ty = worldTransform.ty;

        // xy
        verticies[index++] = a * w1 + c * h1 + tx;
        verticies[index++] = d * h1 + b * w1 + ty;
        // uv
        verticies[index++] = uvs.x0;
        verticies[index++] = uvs.y0;
        // color
        verticies[index++] = alpha;
        verticies[index++] = tint;

        // xy
        verticies[index++] = a * w0 + c * h1 + tx;
        verticies[index++] = d * h1 + b * w0 + ty;
        // uv
        verticies[index++] = uvs.x1;
        verticies[index++] = uvs.y1;
        // color
        verticies[index++] = alpha;
        verticies[index++] = tint;

        // xy
        verticies[index++] = a * w0 + c * h0 + tx;
        verticies[index++] = d * h0 + b * w0 + ty;
        // uv
        verticies[index++] = uvs.x2;
        verticies[index++] = uvs.y2;
        // color
        verticies[index++] = alpha;
        verticies[index++] = tint;

        // xy
        verticies[index++] = a * w1 + c * h0 + tx;
        verticies[index++] = d * h0 + b * w1 + ty;
        // uv
        verticies[index++] = uvs.x3;
        verticies[index++] = uvs.y3;
        // color
        verticies[index++] = alpha;
        verticies[index++] = tint;

        // increment the batchsize
        this.sprites[this.currentBatchSize++] = sprite;

    };

    /**
    * Renders a TilingSprite using the spriteBatch.
    *
    * @method renderTilingSprite
    * @param sprite {TilingSprite} the tilingSprite to render
    */
    PIXI.WebGLSpriteBatch.prototype.renderTilingSprite = function(tilingSprite)
    {
        var texture = tilingSprite.tilingTexture;

        // check texture..
        if(this.currentBatchSize >= this.size)
        {
            //return;
            this.flush();
            this.currentBaseTexture = texture.baseTexture;
        }

         // set the textures uvs temporarily
        // TODO create a separate texture so that we can tile part of a texture

        if(!tilingSprite._uvs)tilingSprite._uvs = new PIXI.TextureUvs();

        var uvs = tilingSprite._uvs;

        tilingSprite.tilePosition.x %= texture.baseTexture.width * tilingSprite.tileScaleOffset.x;
        tilingSprite.tilePosition.y %= texture.baseTexture.height * tilingSprite.tileScaleOffset.y;

        var offsetX =  tilingSprite.tilePosition.x/(texture.baseTexture.width*tilingSprite.tileScaleOffset.x);
        var offsetY =  tilingSprite.tilePosition.y/(texture.baseTexture.height*tilingSprite.tileScaleOffset.y);

        var scaleX =  (tilingSprite.width / texture.baseTexture.width)  / (tilingSprite.tileScale.x * tilingSprite.tileScaleOffset.x);
        var scaleY =  (tilingSprite.height / texture.baseTexture.height) / (tilingSprite.tileScale.y * tilingSprite.tileScaleOffset.y);

        uvs.x0 = 0 - offsetX;
        uvs.y0 = 0 - offsetY;

        uvs.x1 = (1 * scaleX) - offsetX;
        uvs.y1 = 0 - offsetY;

        uvs.x2 = (1 * scaleX) - offsetX;
        uvs.y2 = (1 * scaleY) - offsetY;

        uvs.x3 = 0 - offsetX;
        uvs.y3 = (1 *scaleY) - offsetY;

        // get the tilingSprites current alpha
        var alpha = tilingSprite.worldAlpha;
        var tint = tilingSprite.tint;

        var  verticies = this.vertices;

        var width = tilingSprite.width;
        var height = tilingSprite.height;

        // TODO trim??
        var aX = tilingSprite.anchor.x;
        var aY = tilingSprite.anchor.y;
        var w0 = width * (1-aX);
        var w1 = width * -aX;

        var h0 = height * (1-aY);
        var h1 = height * -aY;

        var index = this.currentBatchSize * 4 * this.vertSize;

        var resolution = texture.baseTexture.resolution;

        var worldTransform = tilingSprite.worldTransform;

        var a = worldTransform.a / resolution;//[0];
        var b = worldTransform.b / resolution;//[3];
        var c = worldTransform.c / resolution;//[1];
        var d = worldTransform.d / resolution;//[4];
        var tx = worldTransform.tx;//[2];
        var ty = worldTransform.ty;///[5];

        // xy
        verticies[index++] = a * w1 + c * h1 + tx;
        verticies[index++] = d * h1 + b * w1 + ty;
        // uv
        verticies[index++] = uvs.x0;
        verticies[index++] = uvs.y0;
        // color
        verticies[index++] = alpha;
        verticies[index++] = tint;

        // xy
        verticies[index++] = (a * w0 + c * h1 + tx);
        verticies[index++] = d * h1 + b * w0 + ty;
        // uv
        verticies[index++] = uvs.x1;
        verticies[index++] = uvs.y1;
        // color
        verticies[index++] = alpha;
        verticies[index++] = tint;

        // xy
        verticies[index++] = a * w0 + c * h0 + tx;
        verticies[index++] = d * h0 + b * w0 + ty;
        // uv
        verticies[index++] = uvs.x2;
        verticies[index++] = uvs.y2;
        // color
        verticies[index++] = alpha;
        verticies[index++] = tint;

        // xy
        verticies[index++] = a * w1 + c * h0 + tx;
        verticies[index++] = d * h0 + b * w1 + ty;
        // uv
        verticies[index++] = uvs.x3;
        verticies[index++] = uvs.y3;
        // color
        verticies[index++] = alpha;
        verticies[index++] = tint;

        // increment the batchsize
        this.sprites[this.currentBatchSize++] = tilingSprite;
    };

    /**
    * Renders the content and empties the current batch.
    *
    * @method flush
    */
    PIXI.WebGLSpriteBatch.prototype.flush = function()
    {
        // If the batch is length 0 then return as there is nothing to draw
        if (this.currentBatchSize===0)return;

        var gl = this.gl;
        var shader;

        if(this.dirty)
        {
            this.dirty = false;
            // bind the main texture
            gl.activeTexture(gl.TEXTURE0);

            // bind the buffers
            gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);

            shader =  this.defaultShader.shaders[gl.id];

            // this is the same for each shader?
            var stride =  this.vertSize * 4;
            gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, stride, 0);
            gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, stride, 2 * 4);
            gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, stride, 4 * 4);
        }

        // upload the verts to the buffer
        if(this.currentBatchSize > ( this.size * 0.5 ) )
        {
            gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices);
        }
        else
        {
            var view = this.vertices.subarray(0, this.currentBatchSize * 4 * this.vertSize);
            gl.bufferSubData(gl.ARRAY_BUFFER, 0, view);
        }

        var nextTexture, nextBlendMode, nextShader;
        var batchSize = 0;
        var start = 0;

        var currentBaseTexture = null;
        var currentBlendMode = this.renderSession.blendModeManager.currentBlendMode;
        var currentShader = null;

        var blendSwap = false;
        var shaderSwap = false;
        var sprite;

        for (var i = 0, j = this.currentBatchSize; i < j; i++) {

            sprite = this.sprites[i];

            nextTexture = sprite.texture.baseTexture;
            nextBlendMode = sprite.blendMode;
            nextShader = sprite.shader || this.defaultShader;

            blendSwap = currentBlendMode !== nextBlendMode;
            shaderSwap = currentShader !== nextShader; // should I use _UIDS???

            if(currentBaseTexture !== nextTexture || blendSwap || shaderSwap)
            {
                this.renderBatch(currentBaseTexture, batchSize, start);

                start = i;
                batchSize = 0;
                currentBaseTexture = nextTexture;

                if( blendSwap )
                {
                    currentBlendMode = nextBlendMode;
                    this.renderSession.blendModeManager.setBlendMode( currentBlendMode );
                }

                if( shaderSwap )
                {
                    currentShader = nextShader;

                    shader = currentShader.shaders[gl.id];

                    if(!shader)
                    {
                        shader = new PIXI.PixiShader(gl);

                        shader.fragmentSrc =currentShader.fragmentSrc;
                        shader.uniforms =currentShader.uniforms;
                        shader.init();

                        currentShader.shaders[gl.id] = shader;
                    }

                    // set shader function???
                    this.renderSession.shaderManager.setShader(shader);

                    if(shader.dirty)shader.syncUniforms();

                    // both thease only need to be set if they are changing..
                    // set the projection
                    var projection = this.renderSession.projection;
                    gl.uniform2f(shader.projectionVector, projection.x, projection.y);

                    // TODO - this is temprorary!
                    var offsetVector = this.renderSession.offset;
                    gl.uniform2f(shader.offsetVector, offsetVector.x, offsetVector.y);

                    // set the pointers
                }
            }

            batchSize++;
        }

        this.renderBatch(currentBaseTexture, batchSize, start);

        // then reset the batch!
        this.currentBatchSize = 0;
    };

    /**
    * @method renderBatch
    * @param texture {Texture}
    * @param size {Number}
    * @param startIndex {Number}
    */
    PIXI.WebGLSpriteBatch.prototype.renderBatch = function(texture, size, startIndex)
    {
        if(size === 0)return;

        var gl = this.gl;

        // check if a texture is dirty..
        if(texture._dirty[gl.id])
        {
            this.renderSession.renderer.updateTexture(texture);
        }
        else
        {
            // bind the current texture
            gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]);
        }

        // now draw those suckas!
        gl.drawElements(gl.TRIANGLES, size * 6, gl.UNSIGNED_SHORT, startIndex * 6 * 2);

        // increment the draw count
        this.renderSession.drawCount++;
    };

    /**
    * @method stop
    */
    PIXI.WebGLSpriteBatch.prototype.stop = function()
    {
        this.flush();
        this.dirty = true;
    };

    /**
    * @method start
    */
    PIXI.WebGLSpriteBatch.prototype.start = function()
    {
        this.dirty = true;
    };

    /**
    * Destroys the SpriteBatch.
    *
    * @method destroy
    */
    PIXI.WebGLSpriteBatch.prototype.destroy = function()
    {
        this.vertices = null;
        this.indices = null;

        this.gl.deleteBuffer( this.vertexBuffer );
        this.gl.deleteBuffer( this.indexBuffer );

        this.currentBaseTexture = null;

        this.gl = null;
    };