diff --git a/src/core/index.js b/src/core/index.js index d533cf9..e373407 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -24,9 +24,9 @@ DisplayObjectContainer: require('./display/Container'), Sprite: require('./sprites/Sprite'), - SpriteBatch: require('./sprites/SpriteBatch'), + ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - SpriteBatchRenderer: require('./sprites/webgl/SpriteBatchRenderer'), + ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // primitives Graphics: require('./graphics/Graphics'), diff --git a/src/core/index.js b/src/core/index.js index d533cf9..e373407 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -24,9 +24,9 @@ DisplayObjectContainer: require('./display/Container'), Sprite: require('./sprites/Sprite'), - SpriteBatch: require('./sprites/SpriteBatch'), + ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - SpriteBatchRenderer: require('./sprites/webgl/SpriteBatchRenderer'), + ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // primitives Graphics: require('./graphics/Graphics'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js new file mode 100644 index 0000000..a7eef53 --- /dev/null +++ b/src/core/particles/ParticleContainer.js @@ -0,0 +1,176 @@ +var Container = require('../display/Container'); + +/** + * The Particle class is a really fast version of the Container built solely for speed, + * so use when you need a lot of sprites or particles. The tradeoff of the Particle is that advanced + * functionality will not work. Particle implements only the basic object transform (position, scale, rotation). + * Any other functionality like tinting, masking, etc will not work on sprites in this batch. + * + * It's extremely easy to use : + * + * ```js + * var container = new Particle(); + * + * for(var i = 0; i < 100; ++i) + * { + * var sprite = new PIXI.Sprite.fromImage("myImage.png"); + * container.addChild(sprite); + * } + * ``` + * + * And here you have a hundred sprites that will be renderer at the speed of light. + * + * @class + * @namespace PIXI + */ + +//TODO RENAME to PARTICLE CONTAINER? +function Particle() +{ + Container.call(this); +} + +Particle.prototype = Object.create(Container.prototype); +Particle.prototype.constructor = Particle; +module.exports = Particle; + +/** + * Updates the object transform for rendering + * + * @private + */ +Particle.prototype.updateTransform = function () +{ + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} The webgl renderer + * @private + */ +Particle.prototype.renderWebGL = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} The canvas renderer + * @private + */ +Particle.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; + } + + var frame = child.texture.frame; + + context.globalAlpha = this.worldAlpha * child.alpha; + + if (child.rotation % (Math.PI * 2) === 0) + { + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx, + transform.ty + ); + + isRotated = false; + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, + ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, + frame.width * child.scale.x, + frame.height * child.scale.y + ); + } + else + { + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx | 0, + childTransform.ty | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx, + childTransform.ty + ); + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width) + 0.5) | 0, + ((child.anchor.y) * (-frame.height) + 0.5) | 0, + frame.width, + frame.height + ); + } + } +}; diff --git a/src/core/index.js b/src/core/index.js index d533cf9..e373407 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -24,9 +24,9 @@ DisplayObjectContainer: require('./display/Container'), Sprite: require('./sprites/Sprite'), - SpriteBatch: require('./sprites/SpriteBatch'), + ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - SpriteBatchRenderer: require('./sprites/webgl/SpriteBatchRenderer'), + ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // primitives Graphics: require('./graphics/Graphics'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js new file mode 100644 index 0000000..a7eef53 --- /dev/null +++ b/src/core/particles/ParticleContainer.js @@ -0,0 +1,176 @@ +var Container = require('../display/Container'); + +/** + * The Particle class is a really fast version of the Container built solely for speed, + * so use when you need a lot of sprites or particles. The tradeoff of the Particle is that advanced + * functionality will not work. Particle implements only the basic object transform (position, scale, rotation). + * Any other functionality like tinting, masking, etc will not work on sprites in this batch. + * + * It's extremely easy to use : + * + * ```js + * var container = new Particle(); + * + * for(var i = 0; i < 100; ++i) + * { + * var sprite = new PIXI.Sprite.fromImage("myImage.png"); + * container.addChild(sprite); + * } + * ``` + * + * And here you have a hundred sprites that will be renderer at the speed of light. + * + * @class + * @namespace PIXI + */ + +//TODO RENAME to PARTICLE CONTAINER? +function Particle() +{ + Container.call(this); +} + +Particle.prototype = Object.create(Container.prototype); +Particle.prototype.constructor = Particle; +module.exports = Particle; + +/** + * Updates the object transform for rendering + * + * @private + */ +Particle.prototype.updateTransform = function () +{ + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} The webgl renderer + * @private + */ +Particle.prototype.renderWebGL = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} The canvas renderer + * @private + */ +Particle.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; + } + + var frame = child.texture.frame; + + context.globalAlpha = this.worldAlpha * child.alpha; + + if (child.rotation % (Math.PI * 2) === 0) + { + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx, + transform.ty + ); + + isRotated = false; + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, + ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, + frame.width * child.scale.x, + frame.height * child.scale.y + ); + } + else + { + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx | 0, + childTransform.ty | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx, + childTransform.ty + ); + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width) + 0.5) | 0, + ((child.anchor.y) * (-frame.height) + 0.5) | 0, + frame.width, + frame.height + ); + } + } +}; diff --git a/src/core/particles/webgl/ParticleBuffer.js b/src/core/particles/webgl/ParticleBuffer.js new file mode 100644 index 0000000..0e82ad4 --- /dev/null +++ b/src/core/particles/webgl/ParticleBuffer.js @@ -0,0 +1,317 @@ + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi 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 + */ + +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this sprite batch works for. + */ +function ParticleBuffer(gl, size ) +{ + this.gl = gl; + + /** + * + * + * @member {number} + */ + this.vertSize = 2; + + /** + * + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = size; + + // the total number of bytes in our batch + var numVerts = this.size * 2 * this.vertByteSize; + + /** + * Holds the vertices + * + * @member {ArrayBuffer} + */ + this.vertices = new Float32Array(numVerts); + this.position = new Float32Array(numVerts); + this.rotation = new Float32Array(numVerts/2); + this.uvs = new Float32Array(numVerts); + this.alpha = new Float32Array(numVerts/2); + + this.initBuffers(); + + +} + +ParticleBuffer.prototype.constructor = ParticleBuffer; +module.exports = ParticleBuffer; + +/** + * Sets up the renderer context and necessary buffers. + * + * @private + * @param gl {WebGLContext} the current WebGL drawing context + */ +ParticleBuffer.prototype.initBuffers = function () +{ + var gl = this.gl; + + // create a couple of buffers + this.vertexBuffer = gl.createBuffer(); + this.positionBuffer = gl.createBuffer(); + this.rotationBuffer = gl.createBuffer(); + this.uvsBuffer = gl.createBuffer(); + this.alphaBuffer = gl.createBuffer(); + + // upload the buffers.. + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.position, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.alpha, gl.DYNAMIC_DRAW); +}; + + +ParticleBuffer.prototype.refresh = function(children, startIndex, amount) +{ + this.uploadVerticies(children,startIndex, amount); + this.uploadRotation(children,startIndex, amount); + this.uploadUvs(children,startIndex, amount); + this.uploadAlpha(children,startIndex, amount); + +}; + +ParticleBuffer.prototype.uploadVerticies = function (children,startIndex, amount) +{ + var vertices = this.vertices, + index = 0, + sprite, + texture, + trim, + sx, + sy, + w0, w1, h0, h1; + + for (var i = 0; i < amount; i++) { + + sprite = children[startIndex + i]; + texture = sprite._texture; + sx = sprite.scale.x; + sy = sprite.scale.y; + + if (texture.trim) + { + // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. + trim = texture.trim; + + w1 = trim.x - sprite.anchor.x * trim.width; + w0 = w1 + texture.crop.width; + + h1 = trim.y - sprite.anchor.y * trim.height; + h0 = h1 + texture.crop.height; + } + else + { + w0 = (texture._frame.width ) * (1-sprite.anchor.x); + w1 = (texture._frame.width ) * -sprite.anchor.x; + + h0 = texture._frame.height * (1-sprite.anchor.y); + h1 = texture._frame.height * -sprite.anchor.y; + } + + vertices[index++] = w1 * sx; + vertices[index++] = h1 * sy; + + vertices[index++] = w0 * sx; + vertices[index++] = h1 * sy; + + vertices[index++] = w0 * sx; + vertices[index++] = h0 * sy; + + vertices[index++] = w1 * sx; + vertices[index++] = h0 * sy; + } + + this.uploadToBuffer(this.vertexBuffer, this.vertices, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadPosition = function (children,startIndex, amount) +{ + var position = this.position, + index = 0, + spritePosition; + + for (var i = 0; i < amount; i++) { + + spritePosition = children[startIndex + i].position; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + } + + + // upload the verts to the buffer + this.uploadToBuffer(this.positionBuffer, this.position, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadRotation = function (children,startIndex, amount) +{ + var rotation = this.rotation, + index = 0, + spriteRotation; + + for (var i = 0; i < amount; i++) { + + spriteRotation = children[startIndex + i].rotation; + + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + } + + this.uploadToBuffer(this.rotationBuffer, this.rotation, 1 * 4, amount); +}; + +ParticleBuffer.prototype.uploadUvs = function (children,startIndex, amount) +{ + var uvs = this.uvs, + index = 0, + textureUvs; + + for (var i = 0; i < amount; i++) { + + textureUvs = children[startIndex + i]._texture._uvs; + + if(textureUvs) + { + uvs[index++] = textureUvs.x0; + uvs[index++] = textureUvs.y0; + + uvs[index++] = textureUvs.x1; + uvs[index++] = textureUvs.y1; + + uvs[index++] = textureUvs.x2; + uvs[index++] = textureUvs.y2; + + uvs[index++] = textureUvs.x3; + uvs[index++] = textureUvs.y3; + } + else + { + index += 8; + } + } + + this.uploadToBuffer(this.uvsBuffer, this.uvs, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadAlpha = function (children,startIndex, amount) +{ + var alpha = this.alpha, + index = 0, + spriteAlpha; + + for (var i = 0; i < amount; i++) { + + spriteAlpha = children[startIndex + i].alpha; + + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + } + + + this.uploadToBuffer(this.alphaBuffer, this.alpha, 1 * 4, amount); + +}; + +ParticleBuffer.prototype.uploadToBuffer = function (buffer, data, dataSize, amount) +{ + var gl = this.gl; + + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + + if (this.currentBatchSize > ( this.size * 0.5 ) ) + { + gl.bufferSubData(gl.ARRAY_BUFFER, 0, data); + } + else + { + var view = data.subarray(0, amount * dataSize); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); + } +}; + +/** + * Starts a new sprite batch. + * + */ +ParticleBuffer.prototype.bind = function (shader) +{ + var gl = this.gl; + + // this is the same for each shader? + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); + gl.vertexAttribPointer(shader.attributes.aPositionCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); + gl.vertexAttribPointer(shader.attributes.aRotation, 1, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); + gl.vertexAttribPointer(shader.attributes.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); + gl.vertexAttribPointer(shader.attributes.aColor, 1, gl.FLOAT, false, 0, 0); + +}; + +/** + * Destroys the SpriteBatch. + * + */ +ParticleBuffer.prototype.destroy = function () +{ + //TODO implement this :) to busy making the fun bits.. +}; diff --git a/src/core/index.js b/src/core/index.js index d533cf9..e373407 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -24,9 +24,9 @@ DisplayObjectContainer: require('./display/Container'), Sprite: require('./sprites/Sprite'), - SpriteBatch: require('./sprites/SpriteBatch'), + ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - SpriteBatchRenderer: require('./sprites/webgl/SpriteBatchRenderer'), + ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // primitives Graphics: require('./graphics/Graphics'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js new file mode 100644 index 0000000..a7eef53 --- /dev/null +++ b/src/core/particles/ParticleContainer.js @@ -0,0 +1,176 @@ +var Container = require('../display/Container'); + +/** + * The Particle class is a really fast version of the Container built solely for speed, + * so use when you need a lot of sprites or particles. The tradeoff of the Particle is that advanced + * functionality will not work. Particle implements only the basic object transform (position, scale, rotation). + * Any other functionality like tinting, masking, etc will not work on sprites in this batch. + * + * It's extremely easy to use : + * + * ```js + * var container = new Particle(); + * + * for(var i = 0; i < 100; ++i) + * { + * var sprite = new PIXI.Sprite.fromImage("myImage.png"); + * container.addChild(sprite); + * } + * ``` + * + * And here you have a hundred sprites that will be renderer at the speed of light. + * + * @class + * @namespace PIXI + */ + +//TODO RENAME to PARTICLE CONTAINER? +function Particle() +{ + Container.call(this); +} + +Particle.prototype = Object.create(Container.prototype); +Particle.prototype.constructor = Particle; +module.exports = Particle; + +/** + * Updates the object transform for rendering + * + * @private + */ +Particle.prototype.updateTransform = function () +{ + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} The webgl renderer + * @private + */ +Particle.prototype.renderWebGL = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} The canvas renderer + * @private + */ +Particle.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; + } + + var frame = child.texture.frame; + + context.globalAlpha = this.worldAlpha * child.alpha; + + if (child.rotation % (Math.PI * 2) === 0) + { + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx, + transform.ty + ); + + isRotated = false; + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, + ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, + frame.width * child.scale.x, + frame.height * child.scale.y + ); + } + else + { + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx | 0, + childTransform.ty | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx, + childTransform.ty + ); + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width) + 0.5) | 0, + ((child.anchor.y) * (-frame.height) + 0.5) | 0, + frame.width, + frame.height + ); + } + } +}; diff --git a/src/core/particles/webgl/ParticleBuffer.js b/src/core/particles/webgl/ParticleBuffer.js new file mode 100644 index 0000000..0e82ad4 --- /dev/null +++ b/src/core/particles/webgl/ParticleBuffer.js @@ -0,0 +1,317 @@ + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi 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 + */ + +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this sprite batch works for. + */ +function ParticleBuffer(gl, size ) +{ + this.gl = gl; + + /** + * + * + * @member {number} + */ + this.vertSize = 2; + + /** + * + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = size; + + // the total number of bytes in our batch + var numVerts = this.size * 2 * this.vertByteSize; + + /** + * Holds the vertices + * + * @member {ArrayBuffer} + */ + this.vertices = new Float32Array(numVerts); + this.position = new Float32Array(numVerts); + this.rotation = new Float32Array(numVerts/2); + this.uvs = new Float32Array(numVerts); + this.alpha = new Float32Array(numVerts/2); + + this.initBuffers(); + + +} + +ParticleBuffer.prototype.constructor = ParticleBuffer; +module.exports = ParticleBuffer; + +/** + * Sets up the renderer context and necessary buffers. + * + * @private + * @param gl {WebGLContext} the current WebGL drawing context + */ +ParticleBuffer.prototype.initBuffers = function () +{ + var gl = this.gl; + + // create a couple of buffers + this.vertexBuffer = gl.createBuffer(); + this.positionBuffer = gl.createBuffer(); + this.rotationBuffer = gl.createBuffer(); + this.uvsBuffer = gl.createBuffer(); + this.alphaBuffer = gl.createBuffer(); + + // upload the buffers.. + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.position, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.alpha, gl.DYNAMIC_DRAW); +}; + + +ParticleBuffer.prototype.refresh = function(children, startIndex, amount) +{ + this.uploadVerticies(children,startIndex, amount); + this.uploadRotation(children,startIndex, amount); + this.uploadUvs(children,startIndex, amount); + this.uploadAlpha(children,startIndex, amount); + +}; + +ParticleBuffer.prototype.uploadVerticies = function (children,startIndex, amount) +{ + var vertices = this.vertices, + index = 0, + sprite, + texture, + trim, + sx, + sy, + w0, w1, h0, h1; + + for (var i = 0; i < amount; i++) { + + sprite = children[startIndex + i]; + texture = sprite._texture; + sx = sprite.scale.x; + sy = sprite.scale.y; + + if (texture.trim) + { + // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. + trim = texture.trim; + + w1 = trim.x - sprite.anchor.x * trim.width; + w0 = w1 + texture.crop.width; + + h1 = trim.y - sprite.anchor.y * trim.height; + h0 = h1 + texture.crop.height; + } + else + { + w0 = (texture._frame.width ) * (1-sprite.anchor.x); + w1 = (texture._frame.width ) * -sprite.anchor.x; + + h0 = texture._frame.height * (1-sprite.anchor.y); + h1 = texture._frame.height * -sprite.anchor.y; + } + + vertices[index++] = w1 * sx; + vertices[index++] = h1 * sy; + + vertices[index++] = w0 * sx; + vertices[index++] = h1 * sy; + + vertices[index++] = w0 * sx; + vertices[index++] = h0 * sy; + + vertices[index++] = w1 * sx; + vertices[index++] = h0 * sy; + } + + this.uploadToBuffer(this.vertexBuffer, this.vertices, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadPosition = function (children,startIndex, amount) +{ + var position = this.position, + index = 0, + spritePosition; + + for (var i = 0; i < amount; i++) { + + spritePosition = children[startIndex + i].position; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + } + + + // upload the verts to the buffer + this.uploadToBuffer(this.positionBuffer, this.position, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadRotation = function (children,startIndex, amount) +{ + var rotation = this.rotation, + index = 0, + spriteRotation; + + for (var i = 0; i < amount; i++) { + + spriteRotation = children[startIndex + i].rotation; + + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + } + + this.uploadToBuffer(this.rotationBuffer, this.rotation, 1 * 4, amount); +}; + +ParticleBuffer.prototype.uploadUvs = function (children,startIndex, amount) +{ + var uvs = this.uvs, + index = 0, + textureUvs; + + for (var i = 0; i < amount; i++) { + + textureUvs = children[startIndex + i]._texture._uvs; + + if(textureUvs) + { + uvs[index++] = textureUvs.x0; + uvs[index++] = textureUvs.y0; + + uvs[index++] = textureUvs.x1; + uvs[index++] = textureUvs.y1; + + uvs[index++] = textureUvs.x2; + uvs[index++] = textureUvs.y2; + + uvs[index++] = textureUvs.x3; + uvs[index++] = textureUvs.y3; + } + else + { + index += 8; + } + } + + this.uploadToBuffer(this.uvsBuffer, this.uvs, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadAlpha = function (children,startIndex, amount) +{ + var alpha = this.alpha, + index = 0, + spriteAlpha; + + for (var i = 0; i < amount; i++) { + + spriteAlpha = children[startIndex + i].alpha; + + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + } + + + this.uploadToBuffer(this.alphaBuffer, this.alpha, 1 * 4, amount); + +}; + +ParticleBuffer.prototype.uploadToBuffer = function (buffer, data, dataSize, amount) +{ + var gl = this.gl; + + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + + if (this.currentBatchSize > ( this.size * 0.5 ) ) + { + gl.bufferSubData(gl.ARRAY_BUFFER, 0, data); + } + else + { + var view = data.subarray(0, amount * dataSize); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); + } +}; + +/** + * Starts a new sprite batch. + * + */ +ParticleBuffer.prototype.bind = function (shader) +{ + var gl = this.gl; + + // this is the same for each shader? + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); + gl.vertexAttribPointer(shader.attributes.aPositionCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); + gl.vertexAttribPointer(shader.attributes.aRotation, 1, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); + gl.vertexAttribPointer(shader.attributes.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); + gl.vertexAttribPointer(shader.attributes.aColor, 1, gl.FLOAT, false, 0, 0); + +}; + +/** + * Destroys the SpriteBatch. + * + */ +ParticleBuffer.prototype.destroy = function () +{ + //TODO implement this :) to busy making the fun bits.. +}; diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js new file mode 100644 index 0000000..29ed7c7 --- /dev/null +++ b/src/core/particles/webgl/ParticleRenderer.js @@ -0,0 +1,464 @@ +var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), + WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), + ParticleShader = require('./ParticleShader'), + ParticleBuffer = require('./ParticleBuffer'), + math = require('../../math'); + +var SpriteDataCache = function () +{ + this.rotation = 0; + this.pX = 0; + this.pY = 0; + this.texture = null; + this.sX = 0; + this.sY = 0; + this.alpha = 0; +}; + + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi 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 ParticleRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleRenderer.java + */ + +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this sprite batch works for. + */ +function ParticleRenderer(renderer) +{ + ObjectRenderer.call(this, renderer); + + + /** + * The number of images in the Particle before it flushes. + * + * @member {number} + */ + this.size = 15000;//CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + this.maxSprites = 200000; + + var numIndices = this.size * 6; + + /** + * Holds the indices + * + * @member {Uint16Array} + */ + this.indices = new Uint16Array(numIndices); + + 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; + } + + /** + * + * + * @member {number} + */ + this.currentBatchSize = 0; + + /** + * + * + * @member {Array} + */ + this.sprites = []; + this.spriteDataCache = []; + + /** + * The default shader that is used if a sprite doesn't have a more specific one. + * + * @member {Shader} + */ + this.shader = null; + + this.buffers = []; + + this.tempMatrix = new math.Matrix(); +} + +ParticleRenderer.prototype = Object.create(ObjectRenderer.prototype); +ParticleRenderer.prototype.constructor = ParticleRenderer; +module.exports = ParticleRenderer; + +WebGLRenderer.registerPlugin('particle', ParticleRenderer); + +/** + * Sets up the renderer context and necessary buffers. + * + * @private + * @param gl {WebGLContext} the current WebGL drawing context + */ +ParticleRenderer.prototype.onContextChange = function () +{ + var gl = this.renderer.gl; + + // setup default shader + this.shader = new ParticleShader(this.renderer.shaderManager); + + 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); + + this.setSize(this.maxSprites); +}; + +ParticleRenderer.prototype.setSize = function ( totalSize ) +{ + var gl = this.renderer.gl; + + var i; + + for (i = 0; i < totalSize; i += this.size) + { + this.buffers.push( new ParticleBuffer(gl, this.size) ); + } + + for (i = 0; i < totalSize; i++) + { + this.spriteDataCache.push(new SpriteDataCache()); + } +}; + + +/** + * Renders the sprite object. + * + * @param sprite {Sprite} the sprite to render when using this Particle + */ +ParticleRenderer.prototype.render = function ( Particle ) +{ + var children = Particle.children; + + // if the uvs have not updated then no point rendering just yet! + //this.renderer.blendModeManager.setBlendMode(sprite.blendMode); + var gl = this.renderer.gl; + + var m = Particle.worldTransform.copy( this.tempMatrix ); + m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); + + gl.uniformMatrix3fv(this.shader.uniforms.projectionMatrix._location, false, m.toArray(true)); + + this.currentBatchSize = 0; +/* + var rotationDirty = false; + var positionDirty = false; + var vertsDirty = false; + var uvsDirty = false; + var alphaDirty = false;*/ + + this.sprites = children; + + this.vertDirty = 0; + + for (var i=0,j= children.length; i this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadVerticies(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadPosition = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+= this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadPosition(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadRotation = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadRotation(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadUvs = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadUvs(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadAlpha = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadAlpha(children, i, amount); + } +}; + +/** + * Renders the content and empties the current batch. + * + */ +ParticleRenderer.prototype.flush = function () +{ + // If the batch is length 0 then return as there is nothing to draw + if (this.currentBatchSize === 0) + { + return; + } + + var nextTexture; + var batchSize = 0; + var start = 0; + + var currentBaseTexture = null; + + var sprite; + + for (var i = 0, j = this.currentBatchSize; i < j; i++) + { + + sprite = this.sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if (currentBaseTexture !== nextTexture) + { + this.renderBatch(currentBaseTexture, batchSize, start); + + start = i; + batchSize = 0; + currentBaseTexture = nextTexture; + } + + batchSize++; + } + + this.renderBatch(currentBaseTexture, batchSize, start); + + // then reset the batch! + this.currentBatchSize = 0; +}; + +/** + * Draws the currently batches sprites. + * + * @private + * @param texture {Texture} + * @param size {number} + * @param startIndex {number} + */ +ParticleRenderer.prototype.renderBatch = function (texture, size, startIndex) +{ + if (size === 0) + { + return; + } + + var gl = this.renderer.gl; + + if (!texture._glTextures[gl.id]) + { + this.renderer.updateTexture(texture); + } + else + { + // bind the current texture + gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); + } + + var j = 0; + for (var i = startIndex; i < size; i+=this.size) { + + this.buffers[j++].bind( this.shader ); + + + var amount = ( size - i) ; + if(amount > this.size) + { + amount = this.size; + } + + // now draw those suckas! + gl.drawElements(gl.TRIANGLES, this.size * 6, gl.UNSIGNED_SHORT, 0);//(startIndex % this.size) * 6 * 2); + } + + + + // increment the draw count + this.renderer.drawCount++; +}; + +/** + * Starts a new sprite batch. + * + */ +ParticleRenderer.prototype.start = function () +{ + var gl = this.renderer.gl; + + // bind the main texture + gl.activeTexture(gl.TEXTURE0); + + // bind the buffers + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + var shader = this.shader; + + this.renderer.shaderManager.setShader(shader); +}; + +/** + * Destroys the Particle. + * + */ +ParticleRenderer.prototype.destroy = function () +{ + this.renderer.gl.deleteBuffer(this.vertexBuffer); + this.renderer.gl.deleteBuffer(this.indexBuffer); + + this.shader.destroy(); + + this.renderer = null; + + this.vertices = null; + this.indices = null; + + this.vertexBuffer = null; + this.indexBuffer = null; + + this.drawing = false; + + this.textures = null; + this.blendModes = null; + this.shaders = null; + this.sprites = null; + this.shader = null; +}; + + diff --git a/src/core/index.js b/src/core/index.js index d533cf9..e373407 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -24,9 +24,9 @@ DisplayObjectContainer: require('./display/Container'), Sprite: require('./sprites/Sprite'), - SpriteBatch: require('./sprites/SpriteBatch'), + ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - SpriteBatchRenderer: require('./sprites/webgl/SpriteBatchRenderer'), + ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // primitives Graphics: require('./graphics/Graphics'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js new file mode 100644 index 0000000..a7eef53 --- /dev/null +++ b/src/core/particles/ParticleContainer.js @@ -0,0 +1,176 @@ +var Container = require('../display/Container'); + +/** + * The Particle class is a really fast version of the Container built solely for speed, + * so use when you need a lot of sprites or particles. The tradeoff of the Particle is that advanced + * functionality will not work. Particle implements only the basic object transform (position, scale, rotation). + * Any other functionality like tinting, masking, etc will not work on sprites in this batch. + * + * It's extremely easy to use : + * + * ```js + * var container = new Particle(); + * + * for(var i = 0; i < 100; ++i) + * { + * var sprite = new PIXI.Sprite.fromImage("myImage.png"); + * container.addChild(sprite); + * } + * ``` + * + * And here you have a hundred sprites that will be renderer at the speed of light. + * + * @class + * @namespace PIXI + */ + +//TODO RENAME to PARTICLE CONTAINER? +function Particle() +{ + Container.call(this); +} + +Particle.prototype = Object.create(Container.prototype); +Particle.prototype.constructor = Particle; +module.exports = Particle; + +/** + * Updates the object transform for rendering + * + * @private + */ +Particle.prototype.updateTransform = function () +{ + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} The webgl renderer + * @private + */ +Particle.prototype.renderWebGL = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} The canvas renderer + * @private + */ +Particle.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; + } + + var frame = child.texture.frame; + + context.globalAlpha = this.worldAlpha * child.alpha; + + if (child.rotation % (Math.PI * 2) === 0) + { + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx, + transform.ty + ); + + isRotated = false; + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, + ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, + frame.width * child.scale.x, + frame.height * child.scale.y + ); + } + else + { + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx | 0, + childTransform.ty | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx, + childTransform.ty + ); + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width) + 0.5) | 0, + ((child.anchor.y) * (-frame.height) + 0.5) | 0, + frame.width, + frame.height + ); + } + } +}; diff --git a/src/core/particles/webgl/ParticleBuffer.js b/src/core/particles/webgl/ParticleBuffer.js new file mode 100644 index 0000000..0e82ad4 --- /dev/null +++ b/src/core/particles/webgl/ParticleBuffer.js @@ -0,0 +1,317 @@ + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi 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 + */ + +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this sprite batch works for. + */ +function ParticleBuffer(gl, size ) +{ + this.gl = gl; + + /** + * + * + * @member {number} + */ + this.vertSize = 2; + + /** + * + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = size; + + // the total number of bytes in our batch + var numVerts = this.size * 2 * this.vertByteSize; + + /** + * Holds the vertices + * + * @member {ArrayBuffer} + */ + this.vertices = new Float32Array(numVerts); + this.position = new Float32Array(numVerts); + this.rotation = new Float32Array(numVerts/2); + this.uvs = new Float32Array(numVerts); + this.alpha = new Float32Array(numVerts/2); + + this.initBuffers(); + + +} + +ParticleBuffer.prototype.constructor = ParticleBuffer; +module.exports = ParticleBuffer; + +/** + * Sets up the renderer context and necessary buffers. + * + * @private + * @param gl {WebGLContext} the current WebGL drawing context + */ +ParticleBuffer.prototype.initBuffers = function () +{ + var gl = this.gl; + + // create a couple of buffers + this.vertexBuffer = gl.createBuffer(); + this.positionBuffer = gl.createBuffer(); + this.rotationBuffer = gl.createBuffer(); + this.uvsBuffer = gl.createBuffer(); + this.alphaBuffer = gl.createBuffer(); + + // upload the buffers.. + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.position, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.alpha, gl.DYNAMIC_DRAW); +}; + + +ParticleBuffer.prototype.refresh = function(children, startIndex, amount) +{ + this.uploadVerticies(children,startIndex, amount); + this.uploadRotation(children,startIndex, amount); + this.uploadUvs(children,startIndex, amount); + this.uploadAlpha(children,startIndex, amount); + +}; + +ParticleBuffer.prototype.uploadVerticies = function (children,startIndex, amount) +{ + var vertices = this.vertices, + index = 0, + sprite, + texture, + trim, + sx, + sy, + w0, w1, h0, h1; + + for (var i = 0; i < amount; i++) { + + sprite = children[startIndex + i]; + texture = sprite._texture; + sx = sprite.scale.x; + sy = sprite.scale.y; + + if (texture.trim) + { + // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. + trim = texture.trim; + + w1 = trim.x - sprite.anchor.x * trim.width; + w0 = w1 + texture.crop.width; + + h1 = trim.y - sprite.anchor.y * trim.height; + h0 = h1 + texture.crop.height; + } + else + { + w0 = (texture._frame.width ) * (1-sprite.anchor.x); + w1 = (texture._frame.width ) * -sprite.anchor.x; + + h0 = texture._frame.height * (1-sprite.anchor.y); + h1 = texture._frame.height * -sprite.anchor.y; + } + + vertices[index++] = w1 * sx; + vertices[index++] = h1 * sy; + + vertices[index++] = w0 * sx; + vertices[index++] = h1 * sy; + + vertices[index++] = w0 * sx; + vertices[index++] = h0 * sy; + + vertices[index++] = w1 * sx; + vertices[index++] = h0 * sy; + } + + this.uploadToBuffer(this.vertexBuffer, this.vertices, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadPosition = function (children,startIndex, amount) +{ + var position = this.position, + index = 0, + spritePosition; + + for (var i = 0; i < amount; i++) { + + spritePosition = children[startIndex + i].position; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + } + + + // upload the verts to the buffer + this.uploadToBuffer(this.positionBuffer, this.position, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadRotation = function (children,startIndex, amount) +{ + var rotation = this.rotation, + index = 0, + spriteRotation; + + for (var i = 0; i < amount; i++) { + + spriteRotation = children[startIndex + i].rotation; + + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + } + + this.uploadToBuffer(this.rotationBuffer, this.rotation, 1 * 4, amount); +}; + +ParticleBuffer.prototype.uploadUvs = function (children,startIndex, amount) +{ + var uvs = this.uvs, + index = 0, + textureUvs; + + for (var i = 0; i < amount; i++) { + + textureUvs = children[startIndex + i]._texture._uvs; + + if(textureUvs) + { + uvs[index++] = textureUvs.x0; + uvs[index++] = textureUvs.y0; + + uvs[index++] = textureUvs.x1; + uvs[index++] = textureUvs.y1; + + uvs[index++] = textureUvs.x2; + uvs[index++] = textureUvs.y2; + + uvs[index++] = textureUvs.x3; + uvs[index++] = textureUvs.y3; + } + else + { + index += 8; + } + } + + this.uploadToBuffer(this.uvsBuffer, this.uvs, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadAlpha = function (children,startIndex, amount) +{ + var alpha = this.alpha, + index = 0, + spriteAlpha; + + for (var i = 0; i < amount; i++) { + + spriteAlpha = children[startIndex + i].alpha; + + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + } + + + this.uploadToBuffer(this.alphaBuffer, this.alpha, 1 * 4, amount); + +}; + +ParticleBuffer.prototype.uploadToBuffer = function (buffer, data, dataSize, amount) +{ + var gl = this.gl; + + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + + if (this.currentBatchSize > ( this.size * 0.5 ) ) + { + gl.bufferSubData(gl.ARRAY_BUFFER, 0, data); + } + else + { + var view = data.subarray(0, amount * dataSize); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); + } +}; + +/** + * Starts a new sprite batch. + * + */ +ParticleBuffer.prototype.bind = function (shader) +{ + var gl = this.gl; + + // this is the same for each shader? + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); + gl.vertexAttribPointer(shader.attributes.aPositionCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); + gl.vertexAttribPointer(shader.attributes.aRotation, 1, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); + gl.vertexAttribPointer(shader.attributes.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); + gl.vertexAttribPointer(shader.attributes.aColor, 1, gl.FLOAT, false, 0, 0); + +}; + +/** + * Destroys the SpriteBatch. + * + */ +ParticleBuffer.prototype.destroy = function () +{ + //TODO implement this :) to busy making the fun bits.. +}; diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js new file mode 100644 index 0000000..29ed7c7 --- /dev/null +++ b/src/core/particles/webgl/ParticleRenderer.js @@ -0,0 +1,464 @@ +var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), + WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), + ParticleShader = require('./ParticleShader'), + ParticleBuffer = require('./ParticleBuffer'), + math = require('../../math'); + +var SpriteDataCache = function () +{ + this.rotation = 0; + this.pX = 0; + this.pY = 0; + this.texture = null; + this.sX = 0; + this.sY = 0; + this.alpha = 0; +}; + + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi 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 ParticleRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleRenderer.java + */ + +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this sprite batch works for. + */ +function ParticleRenderer(renderer) +{ + ObjectRenderer.call(this, renderer); + + + /** + * The number of images in the Particle before it flushes. + * + * @member {number} + */ + this.size = 15000;//CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + this.maxSprites = 200000; + + var numIndices = this.size * 6; + + /** + * Holds the indices + * + * @member {Uint16Array} + */ + this.indices = new Uint16Array(numIndices); + + 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; + } + + /** + * + * + * @member {number} + */ + this.currentBatchSize = 0; + + /** + * + * + * @member {Array} + */ + this.sprites = []; + this.spriteDataCache = []; + + /** + * The default shader that is used if a sprite doesn't have a more specific one. + * + * @member {Shader} + */ + this.shader = null; + + this.buffers = []; + + this.tempMatrix = new math.Matrix(); +} + +ParticleRenderer.prototype = Object.create(ObjectRenderer.prototype); +ParticleRenderer.prototype.constructor = ParticleRenderer; +module.exports = ParticleRenderer; + +WebGLRenderer.registerPlugin('particle', ParticleRenderer); + +/** + * Sets up the renderer context and necessary buffers. + * + * @private + * @param gl {WebGLContext} the current WebGL drawing context + */ +ParticleRenderer.prototype.onContextChange = function () +{ + var gl = this.renderer.gl; + + // setup default shader + this.shader = new ParticleShader(this.renderer.shaderManager); + + 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); + + this.setSize(this.maxSprites); +}; + +ParticleRenderer.prototype.setSize = function ( totalSize ) +{ + var gl = this.renderer.gl; + + var i; + + for (i = 0; i < totalSize; i += this.size) + { + this.buffers.push( new ParticleBuffer(gl, this.size) ); + } + + for (i = 0; i < totalSize; i++) + { + this.spriteDataCache.push(new SpriteDataCache()); + } +}; + + +/** + * Renders the sprite object. + * + * @param sprite {Sprite} the sprite to render when using this Particle + */ +ParticleRenderer.prototype.render = function ( Particle ) +{ + var children = Particle.children; + + // if the uvs have not updated then no point rendering just yet! + //this.renderer.blendModeManager.setBlendMode(sprite.blendMode); + var gl = this.renderer.gl; + + var m = Particle.worldTransform.copy( this.tempMatrix ); + m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); + + gl.uniformMatrix3fv(this.shader.uniforms.projectionMatrix._location, false, m.toArray(true)); + + this.currentBatchSize = 0; +/* + var rotationDirty = false; + var positionDirty = false; + var vertsDirty = false; + var uvsDirty = false; + var alphaDirty = false;*/ + + this.sprites = children; + + this.vertDirty = 0; + + for (var i=0,j= children.length; i this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadVerticies(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadPosition = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+= this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadPosition(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadRotation = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadRotation(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadUvs = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadUvs(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadAlpha = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadAlpha(children, i, amount); + } +}; + +/** + * Renders the content and empties the current batch. + * + */ +ParticleRenderer.prototype.flush = function () +{ + // If the batch is length 0 then return as there is nothing to draw + if (this.currentBatchSize === 0) + { + return; + } + + var nextTexture; + var batchSize = 0; + var start = 0; + + var currentBaseTexture = null; + + var sprite; + + for (var i = 0, j = this.currentBatchSize; i < j; i++) + { + + sprite = this.sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if (currentBaseTexture !== nextTexture) + { + this.renderBatch(currentBaseTexture, batchSize, start); + + start = i; + batchSize = 0; + currentBaseTexture = nextTexture; + } + + batchSize++; + } + + this.renderBatch(currentBaseTexture, batchSize, start); + + // then reset the batch! + this.currentBatchSize = 0; +}; + +/** + * Draws the currently batches sprites. + * + * @private + * @param texture {Texture} + * @param size {number} + * @param startIndex {number} + */ +ParticleRenderer.prototype.renderBatch = function (texture, size, startIndex) +{ + if (size === 0) + { + return; + } + + var gl = this.renderer.gl; + + if (!texture._glTextures[gl.id]) + { + this.renderer.updateTexture(texture); + } + else + { + // bind the current texture + gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); + } + + var j = 0; + for (var i = startIndex; i < size; i+=this.size) { + + this.buffers[j++].bind( this.shader ); + + + var amount = ( size - i) ; + if(amount > this.size) + { + amount = this.size; + } + + // now draw those suckas! + gl.drawElements(gl.TRIANGLES, this.size * 6, gl.UNSIGNED_SHORT, 0);//(startIndex % this.size) * 6 * 2); + } + + + + // increment the draw count + this.renderer.drawCount++; +}; + +/** + * Starts a new sprite batch. + * + */ +ParticleRenderer.prototype.start = function () +{ + var gl = this.renderer.gl; + + // bind the main texture + gl.activeTexture(gl.TEXTURE0); + + // bind the buffers + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + var shader = this.shader; + + this.renderer.shaderManager.setShader(shader); +}; + +/** + * Destroys the Particle. + * + */ +ParticleRenderer.prototype.destroy = function () +{ + this.renderer.gl.deleteBuffer(this.vertexBuffer); + this.renderer.gl.deleteBuffer(this.indexBuffer); + + this.shader.destroy(); + + this.renderer = null; + + this.vertices = null; + this.indices = null; + + this.vertexBuffer = null; + this.indexBuffer = null; + + this.drawing = false; + + this.textures = null; + this.blendModes = null; + this.shaders = null; + this.sprites = null; + this.shader = null; +}; + + diff --git a/src/core/particles/webgl/ParticleShader.js b/src/core/particles/webgl/ParticleShader.js new file mode 100644 index 0000000..97836c2 --- /dev/null +++ b/src/core/particles/webgl/ParticleShader.js @@ -0,0 +1,71 @@ +var TextureShader = require('../../renderers/webgl/shaders/TextureShader'); + +/** + * @class + * @extends Shader + * @namespace PIXI + * @param shaderManager {ShaderManager} The webgl shader manager this shader works for. + */ +function ParticleShader(shaderManager) +{ + TextureShader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + 'attribute float aColor;', + + 'attribute vec2 aPositionCoord;', + 'attribute vec2 aScale;', + 'attribute float aRotation;', + + 'uniform mat3 projectionMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + + 'void main(void){', + ' vec2 v = aVertexPosition;', + + ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', + ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', + ' v = v + aPositionCoord;', + + ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', + + ' vTextureCoord = aTextureCoord;', + ' vColor = aColor;', + '}' + ].join('\n'), + // hello + [ + 'precision lowp float;', + + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + + 'uniform sampler2D uSampler;', + + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', + '}' + ].join('\n'), + // custom uniforms + null, + // custom attributes + { + aPositionCoord: 0, + // aScale: 0, + aRotation: 0 + } + ); + + // TEMP HACK + +} + +ParticleShader.prototype = Object.create(TextureShader.prototype); +ParticleShader.prototype.constructor = ParticleShader; + +module.exports = ParticleShader; diff --git a/src/core/index.js b/src/core/index.js index d533cf9..e373407 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -24,9 +24,9 @@ DisplayObjectContainer: require('./display/Container'), Sprite: require('./sprites/Sprite'), - SpriteBatch: require('./sprites/SpriteBatch'), + ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - SpriteBatchRenderer: require('./sprites/webgl/SpriteBatchRenderer'), + ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // primitives Graphics: require('./graphics/Graphics'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js new file mode 100644 index 0000000..a7eef53 --- /dev/null +++ b/src/core/particles/ParticleContainer.js @@ -0,0 +1,176 @@ +var Container = require('../display/Container'); + +/** + * The Particle class is a really fast version of the Container built solely for speed, + * so use when you need a lot of sprites or particles. The tradeoff of the Particle is that advanced + * functionality will not work. Particle implements only the basic object transform (position, scale, rotation). + * Any other functionality like tinting, masking, etc will not work on sprites in this batch. + * + * It's extremely easy to use : + * + * ```js + * var container = new Particle(); + * + * for(var i = 0; i < 100; ++i) + * { + * var sprite = new PIXI.Sprite.fromImage("myImage.png"); + * container.addChild(sprite); + * } + * ``` + * + * And here you have a hundred sprites that will be renderer at the speed of light. + * + * @class + * @namespace PIXI + */ + +//TODO RENAME to PARTICLE CONTAINER? +function Particle() +{ + Container.call(this); +} + +Particle.prototype = Object.create(Container.prototype); +Particle.prototype.constructor = Particle; +module.exports = Particle; + +/** + * Updates the object transform for rendering + * + * @private + */ +Particle.prototype.updateTransform = function () +{ + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} The webgl renderer + * @private + */ +Particle.prototype.renderWebGL = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} The canvas renderer + * @private + */ +Particle.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; + } + + var frame = child.texture.frame; + + context.globalAlpha = this.worldAlpha * child.alpha; + + if (child.rotation % (Math.PI * 2) === 0) + { + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx, + transform.ty + ); + + isRotated = false; + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, + ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, + frame.width * child.scale.x, + frame.height * child.scale.y + ); + } + else + { + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx | 0, + childTransform.ty | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx, + childTransform.ty + ); + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width) + 0.5) | 0, + ((child.anchor.y) * (-frame.height) + 0.5) | 0, + frame.width, + frame.height + ); + } + } +}; diff --git a/src/core/particles/webgl/ParticleBuffer.js b/src/core/particles/webgl/ParticleBuffer.js new file mode 100644 index 0000000..0e82ad4 --- /dev/null +++ b/src/core/particles/webgl/ParticleBuffer.js @@ -0,0 +1,317 @@ + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi 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 + */ + +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this sprite batch works for. + */ +function ParticleBuffer(gl, size ) +{ + this.gl = gl; + + /** + * + * + * @member {number} + */ + this.vertSize = 2; + + /** + * + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = size; + + // the total number of bytes in our batch + var numVerts = this.size * 2 * this.vertByteSize; + + /** + * Holds the vertices + * + * @member {ArrayBuffer} + */ + this.vertices = new Float32Array(numVerts); + this.position = new Float32Array(numVerts); + this.rotation = new Float32Array(numVerts/2); + this.uvs = new Float32Array(numVerts); + this.alpha = new Float32Array(numVerts/2); + + this.initBuffers(); + + +} + +ParticleBuffer.prototype.constructor = ParticleBuffer; +module.exports = ParticleBuffer; + +/** + * Sets up the renderer context and necessary buffers. + * + * @private + * @param gl {WebGLContext} the current WebGL drawing context + */ +ParticleBuffer.prototype.initBuffers = function () +{ + var gl = this.gl; + + // create a couple of buffers + this.vertexBuffer = gl.createBuffer(); + this.positionBuffer = gl.createBuffer(); + this.rotationBuffer = gl.createBuffer(); + this.uvsBuffer = gl.createBuffer(); + this.alphaBuffer = gl.createBuffer(); + + // upload the buffers.. + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.position, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.alpha, gl.DYNAMIC_DRAW); +}; + + +ParticleBuffer.prototype.refresh = function(children, startIndex, amount) +{ + this.uploadVerticies(children,startIndex, amount); + this.uploadRotation(children,startIndex, amount); + this.uploadUvs(children,startIndex, amount); + this.uploadAlpha(children,startIndex, amount); + +}; + +ParticleBuffer.prototype.uploadVerticies = function (children,startIndex, amount) +{ + var vertices = this.vertices, + index = 0, + sprite, + texture, + trim, + sx, + sy, + w0, w1, h0, h1; + + for (var i = 0; i < amount; i++) { + + sprite = children[startIndex + i]; + texture = sprite._texture; + sx = sprite.scale.x; + sy = sprite.scale.y; + + if (texture.trim) + { + // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. + trim = texture.trim; + + w1 = trim.x - sprite.anchor.x * trim.width; + w0 = w1 + texture.crop.width; + + h1 = trim.y - sprite.anchor.y * trim.height; + h0 = h1 + texture.crop.height; + } + else + { + w0 = (texture._frame.width ) * (1-sprite.anchor.x); + w1 = (texture._frame.width ) * -sprite.anchor.x; + + h0 = texture._frame.height * (1-sprite.anchor.y); + h1 = texture._frame.height * -sprite.anchor.y; + } + + vertices[index++] = w1 * sx; + vertices[index++] = h1 * sy; + + vertices[index++] = w0 * sx; + vertices[index++] = h1 * sy; + + vertices[index++] = w0 * sx; + vertices[index++] = h0 * sy; + + vertices[index++] = w1 * sx; + vertices[index++] = h0 * sy; + } + + this.uploadToBuffer(this.vertexBuffer, this.vertices, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadPosition = function (children,startIndex, amount) +{ + var position = this.position, + index = 0, + spritePosition; + + for (var i = 0; i < amount; i++) { + + spritePosition = children[startIndex + i].position; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + } + + + // upload the verts to the buffer + this.uploadToBuffer(this.positionBuffer, this.position, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadRotation = function (children,startIndex, amount) +{ + var rotation = this.rotation, + index = 0, + spriteRotation; + + for (var i = 0; i < amount; i++) { + + spriteRotation = children[startIndex + i].rotation; + + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + } + + this.uploadToBuffer(this.rotationBuffer, this.rotation, 1 * 4, amount); +}; + +ParticleBuffer.prototype.uploadUvs = function (children,startIndex, amount) +{ + var uvs = this.uvs, + index = 0, + textureUvs; + + for (var i = 0; i < amount; i++) { + + textureUvs = children[startIndex + i]._texture._uvs; + + if(textureUvs) + { + uvs[index++] = textureUvs.x0; + uvs[index++] = textureUvs.y0; + + uvs[index++] = textureUvs.x1; + uvs[index++] = textureUvs.y1; + + uvs[index++] = textureUvs.x2; + uvs[index++] = textureUvs.y2; + + uvs[index++] = textureUvs.x3; + uvs[index++] = textureUvs.y3; + } + else + { + index += 8; + } + } + + this.uploadToBuffer(this.uvsBuffer, this.uvs, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadAlpha = function (children,startIndex, amount) +{ + var alpha = this.alpha, + index = 0, + spriteAlpha; + + for (var i = 0; i < amount; i++) { + + spriteAlpha = children[startIndex + i].alpha; + + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + } + + + this.uploadToBuffer(this.alphaBuffer, this.alpha, 1 * 4, amount); + +}; + +ParticleBuffer.prototype.uploadToBuffer = function (buffer, data, dataSize, amount) +{ + var gl = this.gl; + + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + + if (this.currentBatchSize > ( this.size * 0.5 ) ) + { + gl.bufferSubData(gl.ARRAY_BUFFER, 0, data); + } + else + { + var view = data.subarray(0, amount * dataSize); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); + } +}; + +/** + * Starts a new sprite batch. + * + */ +ParticleBuffer.prototype.bind = function (shader) +{ + var gl = this.gl; + + // this is the same for each shader? + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); + gl.vertexAttribPointer(shader.attributes.aPositionCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); + gl.vertexAttribPointer(shader.attributes.aRotation, 1, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); + gl.vertexAttribPointer(shader.attributes.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); + gl.vertexAttribPointer(shader.attributes.aColor, 1, gl.FLOAT, false, 0, 0); + +}; + +/** + * Destroys the SpriteBatch. + * + */ +ParticleBuffer.prototype.destroy = function () +{ + //TODO implement this :) to busy making the fun bits.. +}; diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js new file mode 100644 index 0000000..29ed7c7 --- /dev/null +++ b/src/core/particles/webgl/ParticleRenderer.js @@ -0,0 +1,464 @@ +var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), + WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), + ParticleShader = require('./ParticleShader'), + ParticleBuffer = require('./ParticleBuffer'), + math = require('../../math'); + +var SpriteDataCache = function () +{ + this.rotation = 0; + this.pX = 0; + this.pY = 0; + this.texture = null; + this.sX = 0; + this.sY = 0; + this.alpha = 0; +}; + + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi 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 ParticleRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleRenderer.java + */ + +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this sprite batch works for. + */ +function ParticleRenderer(renderer) +{ + ObjectRenderer.call(this, renderer); + + + /** + * The number of images in the Particle before it flushes. + * + * @member {number} + */ + this.size = 15000;//CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + this.maxSprites = 200000; + + var numIndices = this.size * 6; + + /** + * Holds the indices + * + * @member {Uint16Array} + */ + this.indices = new Uint16Array(numIndices); + + 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; + } + + /** + * + * + * @member {number} + */ + this.currentBatchSize = 0; + + /** + * + * + * @member {Array} + */ + this.sprites = []; + this.spriteDataCache = []; + + /** + * The default shader that is used if a sprite doesn't have a more specific one. + * + * @member {Shader} + */ + this.shader = null; + + this.buffers = []; + + this.tempMatrix = new math.Matrix(); +} + +ParticleRenderer.prototype = Object.create(ObjectRenderer.prototype); +ParticleRenderer.prototype.constructor = ParticleRenderer; +module.exports = ParticleRenderer; + +WebGLRenderer.registerPlugin('particle', ParticleRenderer); + +/** + * Sets up the renderer context and necessary buffers. + * + * @private + * @param gl {WebGLContext} the current WebGL drawing context + */ +ParticleRenderer.prototype.onContextChange = function () +{ + var gl = this.renderer.gl; + + // setup default shader + this.shader = new ParticleShader(this.renderer.shaderManager); + + 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); + + this.setSize(this.maxSprites); +}; + +ParticleRenderer.prototype.setSize = function ( totalSize ) +{ + var gl = this.renderer.gl; + + var i; + + for (i = 0; i < totalSize; i += this.size) + { + this.buffers.push( new ParticleBuffer(gl, this.size) ); + } + + for (i = 0; i < totalSize; i++) + { + this.spriteDataCache.push(new SpriteDataCache()); + } +}; + + +/** + * Renders the sprite object. + * + * @param sprite {Sprite} the sprite to render when using this Particle + */ +ParticleRenderer.prototype.render = function ( Particle ) +{ + var children = Particle.children; + + // if the uvs have not updated then no point rendering just yet! + //this.renderer.blendModeManager.setBlendMode(sprite.blendMode); + var gl = this.renderer.gl; + + var m = Particle.worldTransform.copy( this.tempMatrix ); + m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); + + gl.uniformMatrix3fv(this.shader.uniforms.projectionMatrix._location, false, m.toArray(true)); + + this.currentBatchSize = 0; +/* + var rotationDirty = false; + var positionDirty = false; + var vertsDirty = false; + var uvsDirty = false; + var alphaDirty = false;*/ + + this.sprites = children; + + this.vertDirty = 0; + + for (var i=0,j= children.length; i this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadVerticies(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadPosition = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+= this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadPosition(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadRotation = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadRotation(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadUvs = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadUvs(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadAlpha = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadAlpha(children, i, amount); + } +}; + +/** + * Renders the content and empties the current batch. + * + */ +ParticleRenderer.prototype.flush = function () +{ + // If the batch is length 0 then return as there is nothing to draw + if (this.currentBatchSize === 0) + { + return; + } + + var nextTexture; + var batchSize = 0; + var start = 0; + + var currentBaseTexture = null; + + var sprite; + + for (var i = 0, j = this.currentBatchSize; i < j; i++) + { + + sprite = this.sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if (currentBaseTexture !== nextTexture) + { + this.renderBatch(currentBaseTexture, batchSize, start); + + start = i; + batchSize = 0; + currentBaseTexture = nextTexture; + } + + batchSize++; + } + + this.renderBatch(currentBaseTexture, batchSize, start); + + // then reset the batch! + this.currentBatchSize = 0; +}; + +/** + * Draws the currently batches sprites. + * + * @private + * @param texture {Texture} + * @param size {number} + * @param startIndex {number} + */ +ParticleRenderer.prototype.renderBatch = function (texture, size, startIndex) +{ + if (size === 0) + { + return; + } + + var gl = this.renderer.gl; + + if (!texture._glTextures[gl.id]) + { + this.renderer.updateTexture(texture); + } + else + { + // bind the current texture + gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); + } + + var j = 0; + for (var i = startIndex; i < size; i+=this.size) { + + this.buffers[j++].bind( this.shader ); + + + var amount = ( size - i) ; + if(amount > this.size) + { + amount = this.size; + } + + // now draw those suckas! + gl.drawElements(gl.TRIANGLES, this.size * 6, gl.UNSIGNED_SHORT, 0);//(startIndex % this.size) * 6 * 2); + } + + + + // increment the draw count + this.renderer.drawCount++; +}; + +/** + * Starts a new sprite batch. + * + */ +ParticleRenderer.prototype.start = function () +{ + var gl = this.renderer.gl; + + // bind the main texture + gl.activeTexture(gl.TEXTURE0); + + // bind the buffers + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + var shader = this.shader; + + this.renderer.shaderManager.setShader(shader); +}; + +/** + * Destroys the Particle. + * + */ +ParticleRenderer.prototype.destroy = function () +{ + this.renderer.gl.deleteBuffer(this.vertexBuffer); + this.renderer.gl.deleteBuffer(this.indexBuffer); + + this.shader.destroy(); + + this.renderer = null; + + this.vertices = null; + this.indices = null; + + this.vertexBuffer = null; + this.indexBuffer = null; + + this.drawing = false; + + this.textures = null; + this.blendModes = null; + this.shaders = null; + this.sprites = null; + this.shader = null; +}; + + diff --git a/src/core/particles/webgl/ParticleShader.js b/src/core/particles/webgl/ParticleShader.js new file mode 100644 index 0000000..97836c2 --- /dev/null +++ b/src/core/particles/webgl/ParticleShader.js @@ -0,0 +1,71 @@ +var TextureShader = require('../../renderers/webgl/shaders/TextureShader'); + +/** + * @class + * @extends Shader + * @namespace PIXI + * @param shaderManager {ShaderManager} The webgl shader manager this shader works for. + */ +function ParticleShader(shaderManager) +{ + TextureShader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + 'attribute float aColor;', + + 'attribute vec2 aPositionCoord;', + 'attribute vec2 aScale;', + 'attribute float aRotation;', + + 'uniform mat3 projectionMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + + 'void main(void){', + ' vec2 v = aVertexPosition;', + + ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', + ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', + ' v = v + aPositionCoord;', + + ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', + + ' vTextureCoord = aTextureCoord;', + ' vColor = aColor;', + '}' + ].join('\n'), + // hello + [ + 'precision lowp float;', + + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + + 'uniform sampler2D uSampler;', + + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', + '}' + ].join('\n'), + // custom uniforms + null, + // custom attributes + { + aPositionCoord: 0, + // aScale: 0, + aRotation: 0 + } + ); + + // TEMP HACK + +} + +ParticleShader.prototype = Object.create(TextureShader.prototype); +ParticleShader.prototype.constructor = ParticleShader; + +module.exports = ParticleShader; diff --git a/src/core/sprites/SpriteBatch.js b/src/core/sprites/SpriteBatch.js deleted file mode 100644 index 4238b4c..0000000 --- a/src/core/sprites/SpriteBatch.js +++ /dev/null @@ -1,180 +0,0 @@ -var Container = require('../display/Container'); - -/** - * The SpriteBatch class is a really fast version of the Container built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced - * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new SpriteBatch(); - * - * for(var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @namespace PIXI - */ - -//TODO RENAME to PARTICLE CONTAINER? -function SpriteBatch() -{ - Container.call(this); -} - -SpriteBatch.prototype = Object.create(Container.prototype); -SpriteBatch.prototype.constructor = SpriteBatch; -module.exports = SpriteBatch; - -/** - * Updates the object transform for rendering - * - * @private - */ -SpriteBatch.prototype.updateTransform = function () -{ - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} The webgl renderer - * @private - */ -SpriteBatch.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - renderer.setObjectRenderer( renderer.plugins.spriteBatch ); - - - - renderer.plugins.spriteBatch.render( this ); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} The canvas renderer - * @private - */ -SpriteBatch.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, - ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, - frame.width * child.scale.x, - frame.height * child.scale.y - ); - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width) + 0.5) | 0, - ((child.anchor.y) * (-frame.height) + 0.5) | 0, - frame.width, - frame.height - ); - } - } -}; diff --git a/src/core/index.js b/src/core/index.js index d533cf9..e373407 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -24,9 +24,9 @@ DisplayObjectContainer: require('./display/Container'), Sprite: require('./sprites/Sprite'), - SpriteBatch: require('./sprites/SpriteBatch'), + ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - SpriteBatchRenderer: require('./sprites/webgl/SpriteBatchRenderer'), + ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // primitives Graphics: require('./graphics/Graphics'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js new file mode 100644 index 0000000..a7eef53 --- /dev/null +++ b/src/core/particles/ParticleContainer.js @@ -0,0 +1,176 @@ +var Container = require('../display/Container'); + +/** + * The Particle class is a really fast version of the Container built solely for speed, + * so use when you need a lot of sprites or particles. The tradeoff of the Particle is that advanced + * functionality will not work. Particle implements only the basic object transform (position, scale, rotation). + * Any other functionality like tinting, masking, etc will not work on sprites in this batch. + * + * It's extremely easy to use : + * + * ```js + * var container = new Particle(); + * + * for(var i = 0; i < 100; ++i) + * { + * var sprite = new PIXI.Sprite.fromImage("myImage.png"); + * container.addChild(sprite); + * } + * ``` + * + * And here you have a hundred sprites that will be renderer at the speed of light. + * + * @class + * @namespace PIXI + */ + +//TODO RENAME to PARTICLE CONTAINER? +function Particle() +{ + Container.call(this); +} + +Particle.prototype = Object.create(Container.prototype); +Particle.prototype.constructor = Particle; +module.exports = Particle; + +/** + * Updates the object transform for rendering + * + * @private + */ +Particle.prototype.updateTransform = function () +{ + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} The webgl renderer + * @private + */ +Particle.prototype.renderWebGL = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} The canvas renderer + * @private + */ +Particle.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; + } + + var frame = child.texture.frame; + + context.globalAlpha = this.worldAlpha * child.alpha; + + if (child.rotation % (Math.PI * 2) === 0) + { + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx, + transform.ty + ); + + isRotated = false; + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, + ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, + frame.width * child.scale.x, + frame.height * child.scale.y + ); + } + else + { + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx | 0, + childTransform.ty | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx, + childTransform.ty + ); + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width) + 0.5) | 0, + ((child.anchor.y) * (-frame.height) + 0.5) | 0, + frame.width, + frame.height + ); + } + } +}; diff --git a/src/core/particles/webgl/ParticleBuffer.js b/src/core/particles/webgl/ParticleBuffer.js new file mode 100644 index 0000000..0e82ad4 --- /dev/null +++ b/src/core/particles/webgl/ParticleBuffer.js @@ -0,0 +1,317 @@ + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi 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 + */ + +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this sprite batch works for. + */ +function ParticleBuffer(gl, size ) +{ + this.gl = gl; + + /** + * + * + * @member {number} + */ + this.vertSize = 2; + + /** + * + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = size; + + // the total number of bytes in our batch + var numVerts = this.size * 2 * this.vertByteSize; + + /** + * Holds the vertices + * + * @member {ArrayBuffer} + */ + this.vertices = new Float32Array(numVerts); + this.position = new Float32Array(numVerts); + this.rotation = new Float32Array(numVerts/2); + this.uvs = new Float32Array(numVerts); + this.alpha = new Float32Array(numVerts/2); + + this.initBuffers(); + + +} + +ParticleBuffer.prototype.constructor = ParticleBuffer; +module.exports = ParticleBuffer; + +/** + * Sets up the renderer context and necessary buffers. + * + * @private + * @param gl {WebGLContext} the current WebGL drawing context + */ +ParticleBuffer.prototype.initBuffers = function () +{ + var gl = this.gl; + + // create a couple of buffers + this.vertexBuffer = gl.createBuffer(); + this.positionBuffer = gl.createBuffer(); + this.rotationBuffer = gl.createBuffer(); + this.uvsBuffer = gl.createBuffer(); + this.alphaBuffer = gl.createBuffer(); + + // upload the buffers.. + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.position, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.alpha, gl.DYNAMIC_DRAW); +}; + + +ParticleBuffer.prototype.refresh = function(children, startIndex, amount) +{ + this.uploadVerticies(children,startIndex, amount); + this.uploadRotation(children,startIndex, amount); + this.uploadUvs(children,startIndex, amount); + this.uploadAlpha(children,startIndex, amount); + +}; + +ParticleBuffer.prototype.uploadVerticies = function (children,startIndex, amount) +{ + var vertices = this.vertices, + index = 0, + sprite, + texture, + trim, + sx, + sy, + w0, w1, h0, h1; + + for (var i = 0; i < amount; i++) { + + sprite = children[startIndex + i]; + texture = sprite._texture; + sx = sprite.scale.x; + sy = sprite.scale.y; + + if (texture.trim) + { + // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. + trim = texture.trim; + + w1 = trim.x - sprite.anchor.x * trim.width; + w0 = w1 + texture.crop.width; + + h1 = trim.y - sprite.anchor.y * trim.height; + h0 = h1 + texture.crop.height; + } + else + { + w0 = (texture._frame.width ) * (1-sprite.anchor.x); + w1 = (texture._frame.width ) * -sprite.anchor.x; + + h0 = texture._frame.height * (1-sprite.anchor.y); + h1 = texture._frame.height * -sprite.anchor.y; + } + + vertices[index++] = w1 * sx; + vertices[index++] = h1 * sy; + + vertices[index++] = w0 * sx; + vertices[index++] = h1 * sy; + + vertices[index++] = w0 * sx; + vertices[index++] = h0 * sy; + + vertices[index++] = w1 * sx; + vertices[index++] = h0 * sy; + } + + this.uploadToBuffer(this.vertexBuffer, this.vertices, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadPosition = function (children,startIndex, amount) +{ + var position = this.position, + index = 0, + spritePosition; + + for (var i = 0; i < amount; i++) { + + spritePosition = children[startIndex + i].position; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + } + + + // upload the verts to the buffer + this.uploadToBuffer(this.positionBuffer, this.position, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadRotation = function (children,startIndex, amount) +{ + var rotation = this.rotation, + index = 0, + spriteRotation; + + for (var i = 0; i < amount; i++) { + + spriteRotation = children[startIndex + i].rotation; + + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + } + + this.uploadToBuffer(this.rotationBuffer, this.rotation, 1 * 4, amount); +}; + +ParticleBuffer.prototype.uploadUvs = function (children,startIndex, amount) +{ + var uvs = this.uvs, + index = 0, + textureUvs; + + for (var i = 0; i < amount; i++) { + + textureUvs = children[startIndex + i]._texture._uvs; + + if(textureUvs) + { + uvs[index++] = textureUvs.x0; + uvs[index++] = textureUvs.y0; + + uvs[index++] = textureUvs.x1; + uvs[index++] = textureUvs.y1; + + uvs[index++] = textureUvs.x2; + uvs[index++] = textureUvs.y2; + + uvs[index++] = textureUvs.x3; + uvs[index++] = textureUvs.y3; + } + else + { + index += 8; + } + } + + this.uploadToBuffer(this.uvsBuffer, this.uvs, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadAlpha = function (children,startIndex, amount) +{ + var alpha = this.alpha, + index = 0, + spriteAlpha; + + for (var i = 0; i < amount; i++) { + + spriteAlpha = children[startIndex + i].alpha; + + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + } + + + this.uploadToBuffer(this.alphaBuffer, this.alpha, 1 * 4, amount); + +}; + +ParticleBuffer.prototype.uploadToBuffer = function (buffer, data, dataSize, amount) +{ + var gl = this.gl; + + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + + if (this.currentBatchSize > ( this.size * 0.5 ) ) + { + gl.bufferSubData(gl.ARRAY_BUFFER, 0, data); + } + else + { + var view = data.subarray(0, amount * dataSize); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); + } +}; + +/** + * Starts a new sprite batch. + * + */ +ParticleBuffer.prototype.bind = function (shader) +{ + var gl = this.gl; + + // this is the same for each shader? + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); + gl.vertexAttribPointer(shader.attributes.aPositionCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); + gl.vertexAttribPointer(shader.attributes.aRotation, 1, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); + gl.vertexAttribPointer(shader.attributes.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); + gl.vertexAttribPointer(shader.attributes.aColor, 1, gl.FLOAT, false, 0, 0); + +}; + +/** + * Destroys the SpriteBatch. + * + */ +ParticleBuffer.prototype.destroy = function () +{ + //TODO implement this :) to busy making the fun bits.. +}; diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js new file mode 100644 index 0000000..29ed7c7 --- /dev/null +++ b/src/core/particles/webgl/ParticleRenderer.js @@ -0,0 +1,464 @@ +var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), + WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), + ParticleShader = require('./ParticleShader'), + ParticleBuffer = require('./ParticleBuffer'), + math = require('../../math'); + +var SpriteDataCache = function () +{ + this.rotation = 0; + this.pX = 0; + this.pY = 0; + this.texture = null; + this.sX = 0; + this.sY = 0; + this.alpha = 0; +}; + + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi 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 ParticleRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleRenderer.java + */ + +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this sprite batch works for. + */ +function ParticleRenderer(renderer) +{ + ObjectRenderer.call(this, renderer); + + + /** + * The number of images in the Particle before it flushes. + * + * @member {number} + */ + this.size = 15000;//CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + this.maxSprites = 200000; + + var numIndices = this.size * 6; + + /** + * Holds the indices + * + * @member {Uint16Array} + */ + this.indices = new Uint16Array(numIndices); + + 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; + } + + /** + * + * + * @member {number} + */ + this.currentBatchSize = 0; + + /** + * + * + * @member {Array} + */ + this.sprites = []; + this.spriteDataCache = []; + + /** + * The default shader that is used if a sprite doesn't have a more specific one. + * + * @member {Shader} + */ + this.shader = null; + + this.buffers = []; + + this.tempMatrix = new math.Matrix(); +} + +ParticleRenderer.prototype = Object.create(ObjectRenderer.prototype); +ParticleRenderer.prototype.constructor = ParticleRenderer; +module.exports = ParticleRenderer; + +WebGLRenderer.registerPlugin('particle', ParticleRenderer); + +/** + * Sets up the renderer context and necessary buffers. + * + * @private + * @param gl {WebGLContext} the current WebGL drawing context + */ +ParticleRenderer.prototype.onContextChange = function () +{ + var gl = this.renderer.gl; + + // setup default shader + this.shader = new ParticleShader(this.renderer.shaderManager); + + 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); + + this.setSize(this.maxSprites); +}; + +ParticleRenderer.prototype.setSize = function ( totalSize ) +{ + var gl = this.renderer.gl; + + var i; + + for (i = 0; i < totalSize; i += this.size) + { + this.buffers.push( new ParticleBuffer(gl, this.size) ); + } + + for (i = 0; i < totalSize; i++) + { + this.spriteDataCache.push(new SpriteDataCache()); + } +}; + + +/** + * Renders the sprite object. + * + * @param sprite {Sprite} the sprite to render when using this Particle + */ +ParticleRenderer.prototype.render = function ( Particle ) +{ + var children = Particle.children; + + // if the uvs have not updated then no point rendering just yet! + //this.renderer.blendModeManager.setBlendMode(sprite.blendMode); + var gl = this.renderer.gl; + + var m = Particle.worldTransform.copy( this.tempMatrix ); + m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); + + gl.uniformMatrix3fv(this.shader.uniforms.projectionMatrix._location, false, m.toArray(true)); + + this.currentBatchSize = 0; +/* + var rotationDirty = false; + var positionDirty = false; + var vertsDirty = false; + var uvsDirty = false; + var alphaDirty = false;*/ + + this.sprites = children; + + this.vertDirty = 0; + + for (var i=0,j= children.length; i this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadVerticies(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadPosition = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+= this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadPosition(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadRotation = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadRotation(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadUvs = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadUvs(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadAlpha = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadAlpha(children, i, amount); + } +}; + +/** + * Renders the content and empties the current batch. + * + */ +ParticleRenderer.prototype.flush = function () +{ + // If the batch is length 0 then return as there is nothing to draw + if (this.currentBatchSize === 0) + { + return; + } + + var nextTexture; + var batchSize = 0; + var start = 0; + + var currentBaseTexture = null; + + var sprite; + + for (var i = 0, j = this.currentBatchSize; i < j; i++) + { + + sprite = this.sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if (currentBaseTexture !== nextTexture) + { + this.renderBatch(currentBaseTexture, batchSize, start); + + start = i; + batchSize = 0; + currentBaseTexture = nextTexture; + } + + batchSize++; + } + + this.renderBatch(currentBaseTexture, batchSize, start); + + // then reset the batch! + this.currentBatchSize = 0; +}; + +/** + * Draws the currently batches sprites. + * + * @private + * @param texture {Texture} + * @param size {number} + * @param startIndex {number} + */ +ParticleRenderer.prototype.renderBatch = function (texture, size, startIndex) +{ + if (size === 0) + { + return; + } + + var gl = this.renderer.gl; + + if (!texture._glTextures[gl.id]) + { + this.renderer.updateTexture(texture); + } + else + { + // bind the current texture + gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); + } + + var j = 0; + for (var i = startIndex; i < size; i+=this.size) { + + this.buffers[j++].bind( this.shader ); + + + var amount = ( size - i) ; + if(amount > this.size) + { + amount = this.size; + } + + // now draw those suckas! + gl.drawElements(gl.TRIANGLES, this.size * 6, gl.UNSIGNED_SHORT, 0);//(startIndex % this.size) * 6 * 2); + } + + + + // increment the draw count + this.renderer.drawCount++; +}; + +/** + * Starts a new sprite batch. + * + */ +ParticleRenderer.prototype.start = function () +{ + var gl = this.renderer.gl; + + // bind the main texture + gl.activeTexture(gl.TEXTURE0); + + // bind the buffers + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + var shader = this.shader; + + this.renderer.shaderManager.setShader(shader); +}; + +/** + * Destroys the Particle. + * + */ +ParticleRenderer.prototype.destroy = function () +{ + this.renderer.gl.deleteBuffer(this.vertexBuffer); + this.renderer.gl.deleteBuffer(this.indexBuffer); + + this.shader.destroy(); + + this.renderer = null; + + this.vertices = null; + this.indices = null; + + this.vertexBuffer = null; + this.indexBuffer = null; + + this.drawing = false; + + this.textures = null; + this.blendModes = null; + this.shaders = null; + this.sprites = null; + this.shader = null; +}; + + diff --git a/src/core/particles/webgl/ParticleShader.js b/src/core/particles/webgl/ParticleShader.js new file mode 100644 index 0000000..97836c2 --- /dev/null +++ b/src/core/particles/webgl/ParticleShader.js @@ -0,0 +1,71 @@ +var TextureShader = require('../../renderers/webgl/shaders/TextureShader'); + +/** + * @class + * @extends Shader + * @namespace PIXI + * @param shaderManager {ShaderManager} The webgl shader manager this shader works for. + */ +function ParticleShader(shaderManager) +{ + TextureShader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + 'attribute float aColor;', + + 'attribute vec2 aPositionCoord;', + 'attribute vec2 aScale;', + 'attribute float aRotation;', + + 'uniform mat3 projectionMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + + 'void main(void){', + ' vec2 v = aVertexPosition;', + + ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', + ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', + ' v = v + aPositionCoord;', + + ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', + + ' vTextureCoord = aTextureCoord;', + ' vColor = aColor;', + '}' + ].join('\n'), + // hello + [ + 'precision lowp float;', + + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + + 'uniform sampler2D uSampler;', + + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', + '}' + ].join('\n'), + // custom uniforms + null, + // custom attributes + { + aPositionCoord: 0, + // aScale: 0, + aRotation: 0 + } + ); + + // TEMP HACK + +} + +ParticleShader.prototype = Object.create(TextureShader.prototype); +ParticleShader.prototype.constructor = ParticleShader; + +module.exports = ParticleShader; diff --git a/src/core/sprites/SpriteBatch.js b/src/core/sprites/SpriteBatch.js deleted file mode 100644 index 4238b4c..0000000 --- a/src/core/sprites/SpriteBatch.js +++ /dev/null @@ -1,180 +0,0 @@ -var Container = require('../display/Container'); - -/** - * The SpriteBatch class is a really fast version of the Container built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced - * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new SpriteBatch(); - * - * for(var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @namespace PIXI - */ - -//TODO RENAME to PARTICLE CONTAINER? -function SpriteBatch() -{ - Container.call(this); -} - -SpriteBatch.prototype = Object.create(Container.prototype); -SpriteBatch.prototype.constructor = SpriteBatch; -module.exports = SpriteBatch; - -/** - * Updates the object transform for rendering - * - * @private - */ -SpriteBatch.prototype.updateTransform = function () -{ - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} The webgl renderer - * @private - */ -SpriteBatch.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - renderer.setObjectRenderer( renderer.plugins.spriteBatch ); - - - - renderer.plugins.spriteBatch.render( this ); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} The canvas renderer - * @private - */ -SpriteBatch.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, - ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, - frame.width * child.scale.x, - frame.height * child.scale.y - ); - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width) + 0.5) | 0, - ((child.anchor.y) * (-frame.height) + 0.5) | 0, - frame.width, - frame.height - ); - } - } -}; diff --git a/src/core/sprites/webgl/SpriteBatchBuffers.js b/src/core/sprites/webgl/SpriteBatchBuffers.js deleted file mode 100644 index e28daf5..0000000 --- a/src/core/sprites/webgl/SpriteBatchBuffers.js +++ /dev/null @@ -1,317 +0,0 @@ - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ - * for creating the original pixi 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 SpriteBatchBuffers: - * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/SpriteBatchBuffers.java - */ - -/** - * - * @class - * @private - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this sprite batch works for. - */ -function SpriteBatchBuffers(gl, size ) -{ - this.gl = gl; - - /** - * - * - * @member {number} - */ - this.vertSize = 2; - - /** - * - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = size; - - // the total number of bytes in our batch - var numVerts = this.size * 2 * this.vertByteSize; - - /** - * Holds the vertices - * - * @member {ArrayBuffer} - */ - this.vertices = new Float32Array(numVerts); - this.position = new Float32Array(numVerts); - this.rotation = new Float32Array(numVerts/2); - this.uvs = new Float32Array(numVerts); - this.alpha = new Float32Array(numVerts/2); - - this.initBuffers(); - - -} - -SpriteBatchBuffers.prototype.constructor = SpriteBatchBuffers; -module.exports = SpriteBatchBuffers; - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - * @param gl {WebGLContext} the current WebGL drawing context - */ -SpriteBatchBuffers.prototype.initBuffers = function () -{ - var gl = this.gl; - - // create a couple of buffers - this.vertexBuffer = gl.createBuffer(); - this.positionBuffer = gl.createBuffer(); - this.rotationBuffer = gl.createBuffer(); - this.uvsBuffer = gl.createBuffer(); - this.alphaBuffer = gl.createBuffer(); - - // upload the buffers.. - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.position, gl.DYNAMIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.DYNAMIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.alpha, gl.DYNAMIC_DRAW); -}; - - -SpriteBatchBuffers.prototype.refresh = function(children, startIndex, amount) -{ - this.uploadVerticies(children,startIndex, amount); - this.uploadRotation(children,startIndex, amount); - this.uploadUvs(children,startIndex, amount); - this.uploadAlpha(children,startIndex, amount); - -}; - -SpriteBatchBuffers.prototype.uploadVerticies = function (children,startIndex, amount) -{ - var vertices = this.vertices, - index = 0, - sprite, - texture, - trim, - sx, - sy, - w0, w1, h0, h1; - - for (var i = 0; i < amount; i++) { - - sprite = children[startIndex + i]; - texture = sprite._texture; - sx = sprite.scale.x; - sy = sprite.scale.y; - - if (texture.trim) - { - // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. - trim = texture.trim; - - w1 = trim.x - sprite.anchor.x * trim.width; - w0 = w1 + texture.crop.width; - - h1 = trim.y - sprite.anchor.y * trim.height; - h0 = h1 + texture.crop.height; - } - else - { - w0 = (texture._frame.width ) * (1-sprite.anchor.x); - w1 = (texture._frame.width ) * -sprite.anchor.x; - - h0 = texture._frame.height * (1-sprite.anchor.y); - h1 = texture._frame.height * -sprite.anchor.y; - } - - vertices[index++] = w1 * sx; - vertices[index++] = h1 * sy; - - vertices[index++] = w0 * sx; - vertices[index++] = h1 * sy; - - vertices[index++] = w0 * sx; - vertices[index++] = h0 * sy; - - vertices[index++] = w1 * sx; - vertices[index++] = h0 * sy; - } - - this.uploadToBuffer(this.vertexBuffer, this.vertices, 2 * 4, amount); -}; - -SpriteBatchBuffers.prototype.uploadPosition = function (children,startIndex, amount) -{ - var position = this.position, - index = 0, - spritePosition; - - for (var i = 0; i < amount; i++) { - - spritePosition = children[startIndex + i].position; - - position[index++] = spritePosition.x; - position[index++] = spritePosition.y; - - position[index++] = spritePosition.x; - position[index++] = spritePosition.y; - - position[index++] = spritePosition.x; - position[index++] = spritePosition.y; - - position[index++] = spritePosition.x; - position[index++] = spritePosition.y; - } - - - // upload the verts to the buffer - this.uploadToBuffer(this.positionBuffer, this.position, 2 * 4, amount); -}; - -SpriteBatchBuffers.prototype.uploadRotation = function (children,startIndex, amount) -{ - var rotation = this.rotation, - index = 0, - spriteRotation; - - for (var i = 0; i < amount; i++) { - - spriteRotation = children[startIndex + i].rotation; - - rotation[index++] = spriteRotation; - rotation[index++] = spriteRotation; - rotation[index++] = spriteRotation; - rotation[index++] = spriteRotation; - } - - this.uploadToBuffer(this.rotationBuffer, this.rotation, 1 * 4, amount); -}; - -SpriteBatchBuffers.prototype.uploadUvs = function (children,startIndex, amount) -{ - var uvs = this.uvs, - index = 0, - textureUvs; - - for (var i = 0; i < amount; i++) { - - textureUvs = children[startIndex + i]._texture._uvs; - - if(textureUvs) - { - uvs[index++] = textureUvs.x0; - uvs[index++] = textureUvs.y0; - - uvs[index++] = textureUvs.x1; - uvs[index++] = textureUvs.y1; - - uvs[index++] = textureUvs.x2; - uvs[index++] = textureUvs.y2; - - uvs[index++] = textureUvs.x3; - uvs[index++] = textureUvs.y3; - } - else - { - index += 8; - } - } - - this.uploadToBuffer(this.uvsBuffer, this.uvs, 2 * 4, amount); -}; - -SpriteBatchBuffers.prototype.uploadAlpha = function (children,startIndex, amount) -{ - var alpha = this.alpha, - index = 0, - spriteAlpha; - - for (var i = 0; i < amount; i++) { - - spriteAlpha = children[startIndex + i].alpha; - - alpha[index++] = spriteAlpha; - alpha[index++] = spriteAlpha; - alpha[index++] = spriteAlpha; - alpha[index++] = spriteAlpha; - } - - - this.uploadToBuffer(this.alphaBuffer, this.alpha, 1 * 4, amount); - -}; - -SpriteBatchBuffers.prototype.uploadToBuffer = function (buffer, data, dataSize, amount) -{ - var gl = this.gl; - - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - - if (this.currentBatchSize > ( this.size * 0.5 ) ) - { - gl.bufferSubData(gl.ARRAY_BUFFER, 0, data); - } - else - { - var view = data.subarray(0, amount * dataSize); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); - } -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteBatchBuffers.prototype.bind = function (shader) -{ - var gl = this.gl; - - // this is the same for each shader? - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); - gl.vertexAttribPointer(shader.attributes.aPositionCoord, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); - gl.vertexAttribPointer(shader.attributes.aRotation, 1, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); - gl.vertexAttribPointer(shader.attributes.aTextureCoord, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); - gl.vertexAttribPointer(shader.attributes.aColor, 1, gl.FLOAT, false, 0, 0); - -}; - -/** - * Destroys the SpriteBatch. - * - */ -SpriteBatchBuffers.prototype.destroy = function () -{ - //TODO implement this :) to busy making the fun bits.. -}; diff --git a/src/core/index.js b/src/core/index.js index d533cf9..e373407 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -24,9 +24,9 @@ DisplayObjectContainer: require('./display/Container'), Sprite: require('./sprites/Sprite'), - SpriteBatch: require('./sprites/SpriteBatch'), + ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - SpriteBatchRenderer: require('./sprites/webgl/SpriteBatchRenderer'), + ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // primitives Graphics: require('./graphics/Graphics'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js new file mode 100644 index 0000000..a7eef53 --- /dev/null +++ b/src/core/particles/ParticleContainer.js @@ -0,0 +1,176 @@ +var Container = require('../display/Container'); + +/** + * The Particle class is a really fast version of the Container built solely for speed, + * so use when you need a lot of sprites or particles. The tradeoff of the Particle is that advanced + * functionality will not work. Particle implements only the basic object transform (position, scale, rotation). + * Any other functionality like tinting, masking, etc will not work on sprites in this batch. + * + * It's extremely easy to use : + * + * ```js + * var container = new Particle(); + * + * for(var i = 0; i < 100; ++i) + * { + * var sprite = new PIXI.Sprite.fromImage("myImage.png"); + * container.addChild(sprite); + * } + * ``` + * + * And here you have a hundred sprites that will be renderer at the speed of light. + * + * @class + * @namespace PIXI + */ + +//TODO RENAME to PARTICLE CONTAINER? +function Particle() +{ + Container.call(this); +} + +Particle.prototype = Object.create(Container.prototype); +Particle.prototype.constructor = Particle; +module.exports = Particle; + +/** + * Updates the object transform for rendering + * + * @private + */ +Particle.prototype.updateTransform = function () +{ + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} The webgl renderer + * @private + */ +Particle.prototype.renderWebGL = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} The canvas renderer + * @private + */ +Particle.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; + } + + var frame = child.texture.frame; + + context.globalAlpha = this.worldAlpha * child.alpha; + + if (child.rotation % (Math.PI * 2) === 0) + { + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx, + transform.ty + ); + + isRotated = false; + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, + ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, + frame.width * child.scale.x, + frame.height * child.scale.y + ); + } + else + { + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx | 0, + childTransform.ty | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx, + childTransform.ty + ); + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width) + 0.5) | 0, + ((child.anchor.y) * (-frame.height) + 0.5) | 0, + frame.width, + frame.height + ); + } + } +}; diff --git a/src/core/particles/webgl/ParticleBuffer.js b/src/core/particles/webgl/ParticleBuffer.js new file mode 100644 index 0000000..0e82ad4 --- /dev/null +++ b/src/core/particles/webgl/ParticleBuffer.js @@ -0,0 +1,317 @@ + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi 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 + */ + +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this sprite batch works for. + */ +function ParticleBuffer(gl, size ) +{ + this.gl = gl; + + /** + * + * + * @member {number} + */ + this.vertSize = 2; + + /** + * + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = size; + + // the total number of bytes in our batch + var numVerts = this.size * 2 * this.vertByteSize; + + /** + * Holds the vertices + * + * @member {ArrayBuffer} + */ + this.vertices = new Float32Array(numVerts); + this.position = new Float32Array(numVerts); + this.rotation = new Float32Array(numVerts/2); + this.uvs = new Float32Array(numVerts); + this.alpha = new Float32Array(numVerts/2); + + this.initBuffers(); + + +} + +ParticleBuffer.prototype.constructor = ParticleBuffer; +module.exports = ParticleBuffer; + +/** + * Sets up the renderer context and necessary buffers. + * + * @private + * @param gl {WebGLContext} the current WebGL drawing context + */ +ParticleBuffer.prototype.initBuffers = function () +{ + var gl = this.gl; + + // create a couple of buffers + this.vertexBuffer = gl.createBuffer(); + this.positionBuffer = gl.createBuffer(); + this.rotationBuffer = gl.createBuffer(); + this.uvsBuffer = gl.createBuffer(); + this.alphaBuffer = gl.createBuffer(); + + // upload the buffers.. + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.position, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.alpha, gl.DYNAMIC_DRAW); +}; + + +ParticleBuffer.prototype.refresh = function(children, startIndex, amount) +{ + this.uploadVerticies(children,startIndex, amount); + this.uploadRotation(children,startIndex, amount); + this.uploadUvs(children,startIndex, amount); + this.uploadAlpha(children,startIndex, amount); + +}; + +ParticleBuffer.prototype.uploadVerticies = function (children,startIndex, amount) +{ + var vertices = this.vertices, + index = 0, + sprite, + texture, + trim, + sx, + sy, + w0, w1, h0, h1; + + for (var i = 0; i < amount; i++) { + + sprite = children[startIndex + i]; + texture = sprite._texture; + sx = sprite.scale.x; + sy = sprite.scale.y; + + if (texture.trim) + { + // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. + trim = texture.trim; + + w1 = trim.x - sprite.anchor.x * trim.width; + w0 = w1 + texture.crop.width; + + h1 = trim.y - sprite.anchor.y * trim.height; + h0 = h1 + texture.crop.height; + } + else + { + w0 = (texture._frame.width ) * (1-sprite.anchor.x); + w1 = (texture._frame.width ) * -sprite.anchor.x; + + h0 = texture._frame.height * (1-sprite.anchor.y); + h1 = texture._frame.height * -sprite.anchor.y; + } + + vertices[index++] = w1 * sx; + vertices[index++] = h1 * sy; + + vertices[index++] = w0 * sx; + vertices[index++] = h1 * sy; + + vertices[index++] = w0 * sx; + vertices[index++] = h0 * sy; + + vertices[index++] = w1 * sx; + vertices[index++] = h0 * sy; + } + + this.uploadToBuffer(this.vertexBuffer, this.vertices, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadPosition = function (children,startIndex, amount) +{ + var position = this.position, + index = 0, + spritePosition; + + for (var i = 0; i < amount; i++) { + + spritePosition = children[startIndex + i].position; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + } + + + // upload the verts to the buffer + this.uploadToBuffer(this.positionBuffer, this.position, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadRotation = function (children,startIndex, amount) +{ + var rotation = this.rotation, + index = 0, + spriteRotation; + + for (var i = 0; i < amount; i++) { + + spriteRotation = children[startIndex + i].rotation; + + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + } + + this.uploadToBuffer(this.rotationBuffer, this.rotation, 1 * 4, amount); +}; + +ParticleBuffer.prototype.uploadUvs = function (children,startIndex, amount) +{ + var uvs = this.uvs, + index = 0, + textureUvs; + + for (var i = 0; i < amount; i++) { + + textureUvs = children[startIndex + i]._texture._uvs; + + if(textureUvs) + { + uvs[index++] = textureUvs.x0; + uvs[index++] = textureUvs.y0; + + uvs[index++] = textureUvs.x1; + uvs[index++] = textureUvs.y1; + + uvs[index++] = textureUvs.x2; + uvs[index++] = textureUvs.y2; + + uvs[index++] = textureUvs.x3; + uvs[index++] = textureUvs.y3; + } + else + { + index += 8; + } + } + + this.uploadToBuffer(this.uvsBuffer, this.uvs, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadAlpha = function (children,startIndex, amount) +{ + var alpha = this.alpha, + index = 0, + spriteAlpha; + + for (var i = 0; i < amount; i++) { + + spriteAlpha = children[startIndex + i].alpha; + + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + } + + + this.uploadToBuffer(this.alphaBuffer, this.alpha, 1 * 4, amount); + +}; + +ParticleBuffer.prototype.uploadToBuffer = function (buffer, data, dataSize, amount) +{ + var gl = this.gl; + + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + + if (this.currentBatchSize > ( this.size * 0.5 ) ) + { + gl.bufferSubData(gl.ARRAY_BUFFER, 0, data); + } + else + { + var view = data.subarray(0, amount * dataSize); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); + } +}; + +/** + * Starts a new sprite batch. + * + */ +ParticleBuffer.prototype.bind = function (shader) +{ + var gl = this.gl; + + // this is the same for each shader? + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); + gl.vertexAttribPointer(shader.attributes.aPositionCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); + gl.vertexAttribPointer(shader.attributes.aRotation, 1, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); + gl.vertexAttribPointer(shader.attributes.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); + gl.vertexAttribPointer(shader.attributes.aColor, 1, gl.FLOAT, false, 0, 0); + +}; + +/** + * Destroys the SpriteBatch. + * + */ +ParticleBuffer.prototype.destroy = function () +{ + //TODO implement this :) to busy making the fun bits.. +}; diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js new file mode 100644 index 0000000..29ed7c7 --- /dev/null +++ b/src/core/particles/webgl/ParticleRenderer.js @@ -0,0 +1,464 @@ +var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), + WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), + ParticleShader = require('./ParticleShader'), + ParticleBuffer = require('./ParticleBuffer'), + math = require('../../math'); + +var SpriteDataCache = function () +{ + this.rotation = 0; + this.pX = 0; + this.pY = 0; + this.texture = null; + this.sX = 0; + this.sY = 0; + this.alpha = 0; +}; + + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi 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 ParticleRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleRenderer.java + */ + +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this sprite batch works for. + */ +function ParticleRenderer(renderer) +{ + ObjectRenderer.call(this, renderer); + + + /** + * The number of images in the Particle before it flushes. + * + * @member {number} + */ + this.size = 15000;//CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + this.maxSprites = 200000; + + var numIndices = this.size * 6; + + /** + * Holds the indices + * + * @member {Uint16Array} + */ + this.indices = new Uint16Array(numIndices); + + 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; + } + + /** + * + * + * @member {number} + */ + this.currentBatchSize = 0; + + /** + * + * + * @member {Array} + */ + this.sprites = []; + this.spriteDataCache = []; + + /** + * The default shader that is used if a sprite doesn't have a more specific one. + * + * @member {Shader} + */ + this.shader = null; + + this.buffers = []; + + this.tempMatrix = new math.Matrix(); +} + +ParticleRenderer.prototype = Object.create(ObjectRenderer.prototype); +ParticleRenderer.prototype.constructor = ParticleRenderer; +module.exports = ParticleRenderer; + +WebGLRenderer.registerPlugin('particle', ParticleRenderer); + +/** + * Sets up the renderer context and necessary buffers. + * + * @private + * @param gl {WebGLContext} the current WebGL drawing context + */ +ParticleRenderer.prototype.onContextChange = function () +{ + var gl = this.renderer.gl; + + // setup default shader + this.shader = new ParticleShader(this.renderer.shaderManager); + + 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); + + this.setSize(this.maxSprites); +}; + +ParticleRenderer.prototype.setSize = function ( totalSize ) +{ + var gl = this.renderer.gl; + + var i; + + for (i = 0; i < totalSize; i += this.size) + { + this.buffers.push( new ParticleBuffer(gl, this.size) ); + } + + for (i = 0; i < totalSize; i++) + { + this.spriteDataCache.push(new SpriteDataCache()); + } +}; + + +/** + * Renders the sprite object. + * + * @param sprite {Sprite} the sprite to render when using this Particle + */ +ParticleRenderer.prototype.render = function ( Particle ) +{ + var children = Particle.children; + + // if the uvs have not updated then no point rendering just yet! + //this.renderer.blendModeManager.setBlendMode(sprite.blendMode); + var gl = this.renderer.gl; + + var m = Particle.worldTransform.copy( this.tempMatrix ); + m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); + + gl.uniformMatrix3fv(this.shader.uniforms.projectionMatrix._location, false, m.toArray(true)); + + this.currentBatchSize = 0; +/* + var rotationDirty = false; + var positionDirty = false; + var vertsDirty = false; + var uvsDirty = false; + var alphaDirty = false;*/ + + this.sprites = children; + + this.vertDirty = 0; + + for (var i=0,j= children.length; i this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadVerticies(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadPosition = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+= this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadPosition(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadRotation = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadRotation(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadUvs = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadUvs(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadAlpha = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadAlpha(children, i, amount); + } +}; + +/** + * Renders the content and empties the current batch. + * + */ +ParticleRenderer.prototype.flush = function () +{ + // If the batch is length 0 then return as there is nothing to draw + if (this.currentBatchSize === 0) + { + return; + } + + var nextTexture; + var batchSize = 0; + var start = 0; + + var currentBaseTexture = null; + + var sprite; + + for (var i = 0, j = this.currentBatchSize; i < j; i++) + { + + sprite = this.sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if (currentBaseTexture !== nextTexture) + { + this.renderBatch(currentBaseTexture, batchSize, start); + + start = i; + batchSize = 0; + currentBaseTexture = nextTexture; + } + + batchSize++; + } + + this.renderBatch(currentBaseTexture, batchSize, start); + + // then reset the batch! + this.currentBatchSize = 0; +}; + +/** + * Draws the currently batches sprites. + * + * @private + * @param texture {Texture} + * @param size {number} + * @param startIndex {number} + */ +ParticleRenderer.prototype.renderBatch = function (texture, size, startIndex) +{ + if (size === 0) + { + return; + } + + var gl = this.renderer.gl; + + if (!texture._glTextures[gl.id]) + { + this.renderer.updateTexture(texture); + } + else + { + // bind the current texture + gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); + } + + var j = 0; + for (var i = startIndex; i < size; i+=this.size) { + + this.buffers[j++].bind( this.shader ); + + + var amount = ( size - i) ; + if(amount > this.size) + { + amount = this.size; + } + + // now draw those suckas! + gl.drawElements(gl.TRIANGLES, this.size * 6, gl.UNSIGNED_SHORT, 0);//(startIndex % this.size) * 6 * 2); + } + + + + // increment the draw count + this.renderer.drawCount++; +}; + +/** + * Starts a new sprite batch. + * + */ +ParticleRenderer.prototype.start = function () +{ + var gl = this.renderer.gl; + + // bind the main texture + gl.activeTexture(gl.TEXTURE0); + + // bind the buffers + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + var shader = this.shader; + + this.renderer.shaderManager.setShader(shader); +}; + +/** + * Destroys the Particle. + * + */ +ParticleRenderer.prototype.destroy = function () +{ + this.renderer.gl.deleteBuffer(this.vertexBuffer); + this.renderer.gl.deleteBuffer(this.indexBuffer); + + this.shader.destroy(); + + this.renderer = null; + + this.vertices = null; + this.indices = null; + + this.vertexBuffer = null; + this.indexBuffer = null; + + this.drawing = false; + + this.textures = null; + this.blendModes = null; + this.shaders = null; + this.sprites = null; + this.shader = null; +}; + + diff --git a/src/core/particles/webgl/ParticleShader.js b/src/core/particles/webgl/ParticleShader.js new file mode 100644 index 0000000..97836c2 --- /dev/null +++ b/src/core/particles/webgl/ParticleShader.js @@ -0,0 +1,71 @@ +var TextureShader = require('../../renderers/webgl/shaders/TextureShader'); + +/** + * @class + * @extends Shader + * @namespace PIXI + * @param shaderManager {ShaderManager} The webgl shader manager this shader works for. + */ +function ParticleShader(shaderManager) +{ + TextureShader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + 'attribute float aColor;', + + 'attribute vec2 aPositionCoord;', + 'attribute vec2 aScale;', + 'attribute float aRotation;', + + 'uniform mat3 projectionMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + + 'void main(void){', + ' vec2 v = aVertexPosition;', + + ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', + ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', + ' v = v + aPositionCoord;', + + ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', + + ' vTextureCoord = aTextureCoord;', + ' vColor = aColor;', + '}' + ].join('\n'), + // hello + [ + 'precision lowp float;', + + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + + 'uniform sampler2D uSampler;', + + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', + '}' + ].join('\n'), + // custom uniforms + null, + // custom attributes + { + aPositionCoord: 0, + // aScale: 0, + aRotation: 0 + } + ); + + // TEMP HACK + +} + +ParticleShader.prototype = Object.create(TextureShader.prototype); +ParticleShader.prototype.constructor = ParticleShader; + +module.exports = ParticleShader; diff --git a/src/core/sprites/SpriteBatch.js b/src/core/sprites/SpriteBatch.js deleted file mode 100644 index 4238b4c..0000000 --- a/src/core/sprites/SpriteBatch.js +++ /dev/null @@ -1,180 +0,0 @@ -var Container = require('../display/Container'); - -/** - * The SpriteBatch class is a really fast version of the Container built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced - * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new SpriteBatch(); - * - * for(var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @namespace PIXI - */ - -//TODO RENAME to PARTICLE CONTAINER? -function SpriteBatch() -{ - Container.call(this); -} - -SpriteBatch.prototype = Object.create(Container.prototype); -SpriteBatch.prototype.constructor = SpriteBatch; -module.exports = SpriteBatch; - -/** - * Updates the object transform for rendering - * - * @private - */ -SpriteBatch.prototype.updateTransform = function () -{ - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} The webgl renderer - * @private - */ -SpriteBatch.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - renderer.setObjectRenderer( renderer.plugins.spriteBatch ); - - - - renderer.plugins.spriteBatch.render( this ); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} The canvas renderer - * @private - */ -SpriteBatch.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, - ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, - frame.width * child.scale.x, - frame.height * child.scale.y - ); - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width) + 0.5) | 0, - ((child.anchor.y) * (-frame.height) + 0.5) | 0, - frame.width, - frame.height - ); - } - } -}; diff --git a/src/core/sprites/webgl/SpriteBatchBuffers.js b/src/core/sprites/webgl/SpriteBatchBuffers.js deleted file mode 100644 index e28daf5..0000000 --- a/src/core/sprites/webgl/SpriteBatchBuffers.js +++ /dev/null @@ -1,317 +0,0 @@ - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ - * for creating the original pixi 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 SpriteBatchBuffers: - * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/SpriteBatchBuffers.java - */ - -/** - * - * @class - * @private - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this sprite batch works for. - */ -function SpriteBatchBuffers(gl, size ) -{ - this.gl = gl; - - /** - * - * - * @member {number} - */ - this.vertSize = 2; - - /** - * - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = size; - - // the total number of bytes in our batch - var numVerts = this.size * 2 * this.vertByteSize; - - /** - * Holds the vertices - * - * @member {ArrayBuffer} - */ - this.vertices = new Float32Array(numVerts); - this.position = new Float32Array(numVerts); - this.rotation = new Float32Array(numVerts/2); - this.uvs = new Float32Array(numVerts); - this.alpha = new Float32Array(numVerts/2); - - this.initBuffers(); - - -} - -SpriteBatchBuffers.prototype.constructor = SpriteBatchBuffers; -module.exports = SpriteBatchBuffers; - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - * @param gl {WebGLContext} the current WebGL drawing context - */ -SpriteBatchBuffers.prototype.initBuffers = function () -{ - var gl = this.gl; - - // create a couple of buffers - this.vertexBuffer = gl.createBuffer(); - this.positionBuffer = gl.createBuffer(); - this.rotationBuffer = gl.createBuffer(); - this.uvsBuffer = gl.createBuffer(); - this.alphaBuffer = gl.createBuffer(); - - // upload the buffers.. - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.position, gl.DYNAMIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.DYNAMIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.alpha, gl.DYNAMIC_DRAW); -}; - - -SpriteBatchBuffers.prototype.refresh = function(children, startIndex, amount) -{ - this.uploadVerticies(children,startIndex, amount); - this.uploadRotation(children,startIndex, amount); - this.uploadUvs(children,startIndex, amount); - this.uploadAlpha(children,startIndex, amount); - -}; - -SpriteBatchBuffers.prototype.uploadVerticies = function (children,startIndex, amount) -{ - var vertices = this.vertices, - index = 0, - sprite, - texture, - trim, - sx, - sy, - w0, w1, h0, h1; - - for (var i = 0; i < amount; i++) { - - sprite = children[startIndex + i]; - texture = sprite._texture; - sx = sprite.scale.x; - sy = sprite.scale.y; - - if (texture.trim) - { - // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. - trim = texture.trim; - - w1 = trim.x - sprite.anchor.x * trim.width; - w0 = w1 + texture.crop.width; - - h1 = trim.y - sprite.anchor.y * trim.height; - h0 = h1 + texture.crop.height; - } - else - { - w0 = (texture._frame.width ) * (1-sprite.anchor.x); - w1 = (texture._frame.width ) * -sprite.anchor.x; - - h0 = texture._frame.height * (1-sprite.anchor.y); - h1 = texture._frame.height * -sprite.anchor.y; - } - - vertices[index++] = w1 * sx; - vertices[index++] = h1 * sy; - - vertices[index++] = w0 * sx; - vertices[index++] = h1 * sy; - - vertices[index++] = w0 * sx; - vertices[index++] = h0 * sy; - - vertices[index++] = w1 * sx; - vertices[index++] = h0 * sy; - } - - this.uploadToBuffer(this.vertexBuffer, this.vertices, 2 * 4, amount); -}; - -SpriteBatchBuffers.prototype.uploadPosition = function (children,startIndex, amount) -{ - var position = this.position, - index = 0, - spritePosition; - - for (var i = 0; i < amount; i++) { - - spritePosition = children[startIndex + i].position; - - position[index++] = spritePosition.x; - position[index++] = spritePosition.y; - - position[index++] = spritePosition.x; - position[index++] = spritePosition.y; - - position[index++] = spritePosition.x; - position[index++] = spritePosition.y; - - position[index++] = spritePosition.x; - position[index++] = spritePosition.y; - } - - - // upload the verts to the buffer - this.uploadToBuffer(this.positionBuffer, this.position, 2 * 4, amount); -}; - -SpriteBatchBuffers.prototype.uploadRotation = function (children,startIndex, amount) -{ - var rotation = this.rotation, - index = 0, - spriteRotation; - - for (var i = 0; i < amount; i++) { - - spriteRotation = children[startIndex + i].rotation; - - rotation[index++] = spriteRotation; - rotation[index++] = spriteRotation; - rotation[index++] = spriteRotation; - rotation[index++] = spriteRotation; - } - - this.uploadToBuffer(this.rotationBuffer, this.rotation, 1 * 4, amount); -}; - -SpriteBatchBuffers.prototype.uploadUvs = function (children,startIndex, amount) -{ - var uvs = this.uvs, - index = 0, - textureUvs; - - for (var i = 0; i < amount; i++) { - - textureUvs = children[startIndex + i]._texture._uvs; - - if(textureUvs) - { - uvs[index++] = textureUvs.x0; - uvs[index++] = textureUvs.y0; - - uvs[index++] = textureUvs.x1; - uvs[index++] = textureUvs.y1; - - uvs[index++] = textureUvs.x2; - uvs[index++] = textureUvs.y2; - - uvs[index++] = textureUvs.x3; - uvs[index++] = textureUvs.y3; - } - else - { - index += 8; - } - } - - this.uploadToBuffer(this.uvsBuffer, this.uvs, 2 * 4, amount); -}; - -SpriteBatchBuffers.prototype.uploadAlpha = function (children,startIndex, amount) -{ - var alpha = this.alpha, - index = 0, - spriteAlpha; - - for (var i = 0; i < amount; i++) { - - spriteAlpha = children[startIndex + i].alpha; - - alpha[index++] = spriteAlpha; - alpha[index++] = spriteAlpha; - alpha[index++] = spriteAlpha; - alpha[index++] = spriteAlpha; - } - - - this.uploadToBuffer(this.alphaBuffer, this.alpha, 1 * 4, amount); - -}; - -SpriteBatchBuffers.prototype.uploadToBuffer = function (buffer, data, dataSize, amount) -{ - var gl = this.gl; - - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - - if (this.currentBatchSize > ( this.size * 0.5 ) ) - { - gl.bufferSubData(gl.ARRAY_BUFFER, 0, data); - } - else - { - var view = data.subarray(0, amount * dataSize); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); - } -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteBatchBuffers.prototype.bind = function (shader) -{ - var gl = this.gl; - - // this is the same for each shader? - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); - gl.vertexAttribPointer(shader.attributes.aPositionCoord, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); - gl.vertexAttribPointer(shader.attributes.aRotation, 1, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); - gl.vertexAttribPointer(shader.attributes.aTextureCoord, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); - gl.vertexAttribPointer(shader.attributes.aColor, 1, gl.FLOAT, false, 0, 0); - -}; - -/** - * Destroys the SpriteBatch. - * - */ -SpriteBatchBuffers.prototype.destroy = function () -{ - //TODO implement this :) to busy making the fun bits.. -}; diff --git a/src/core/sprites/webgl/SpriteBatchRenderer.js b/src/core/sprites/webgl/SpriteBatchRenderer.js deleted file mode 100644 index e37b573..0000000 --- a/src/core/sprites/webgl/SpriteBatchRenderer.js +++ /dev/null @@ -1,459 +0,0 @@ -var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), - WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), - SpriteBatchShader = require('./SpriteBatchShader'), - SpriteBatchBuffers = require('./SpriteBatchBuffers'), - math = require('../../math'); - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ - * for creating the original pixi 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 SpriteBatchRenderer: - * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/SpriteBatchRenderer.java - */ - -/** - * - * @class - * @private - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this sprite batch works for. - */ -function SpriteBatchRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = 15000;//CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - this.maxSprites = 200000; - - var numIndices = this.size * 6; - - /** - * Holds the indices - * - * @member {Uint16Array} - */ - this.indices = new Uint16Array(numIndices); - - 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; - } - - /** - * - * - * @member {number} - */ - this.currentBatchSize = 0; - - /** - * - * - * @member {Array} - */ - this.sprites = []; - this.spriteDataCache = []; - - /** - * The default shader that is used if a sprite doesn't have a more specific one. - * - * @member {Shader} - */ - this.shader = null; - - this.buffers = []; - - this.tempMatrix = new math.Matrix(); -} - -SpriteBatchRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteBatchRenderer.prototype.constructor = SpriteBatchRenderer; -module.exports = SpriteBatchRenderer; - -WebGLRenderer.registerPlugin('spriteBatch', SpriteBatchRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - * @param gl {WebGLContext} the current WebGL drawing context - */ -SpriteBatchRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // setup default shader - this.shader = new SpriteBatchShader(this.renderer.shaderManager); - - 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); - - this.setSize(this.maxSprites); -}; - -SpriteBatchRenderer.prototype.setSize = function ( totalSize ) -{ - var gl = this.renderer.gl; - - for (var i = 0; i < totalSize; i += this.size) - { - this.buffers.push( new SpriteBatchBuffers(gl, this.size) ); - } - - for (var i = 0; i < totalSize; i++) - { - this.spriteDataCache.push(new SpriteDataCache()); - }; -}; - - -/** - * Renders the sprite object. - * - * @param sprite {Sprite} the sprite to render when using this spritebatch - */ -SpriteBatchRenderer.prototype.render = function ( spriteBatch ) -{ - var children = spriteBatch.children; - - // if the uvs have not updated then no point rendering just yet! - //this.renderer.blendModeManager.setBlendMode(sprite.blendMode); - var gl = this.renderer.gl; - - var m = spriteBatch.worldTransform.copy( this.tempMatrix ); - m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); - - gl.uniformMatrix3fv(this.shader.uniforms.projectionMatrix._location, false, m.toArray(true)); - - this.currentBatchSize = 0; - - var rotationDirty = false; - var positionDirty = false; - var vertsDirty = false; - var uvsDirty = false; - var alphaDirty = false; - - this.sprites = children; - - this.vertDirty = 0; - - for (var i=0,j= children.length; i this.size) - { - amount = this.size; - } - - this.buffers[j++].uploadVerticies(children, i, amount); - } -}; - -SpriteBatchRenderer.prototype.uploadPosition = function (children) -{ - var j = 0; - for (var i = 0; i < children.length; i+= this.size) - { - var amount = ( children.length - i ); - if(amount > this.size) - { - amount = this.size; - } - - this.buffers[j++].uploadPosition(children, i, amount); - } -}; - -SpriteBatchRenderer.prototype.uploadRotation = function (children) -{ - var j = 0; - for (var i = 0; i < children.length; i+=this.size) - { - var amount = ( children.length - i ); - if(amount > this.size) - { - amount = this.size; - } - - this.buffers[j++].uploadRotation(children, i, amount); - } -}; - -SpriteBatchRenderer.prototype.uploadUvs = function (children) -{ - var j = 0; - for (var i = 0; i < children.length; i+=this.size) - { - var amount = ( children.length - i ); - if(amount > this.size) - { - amount = this.size; - } - - this.buffers[j++].uploadUvs(children, i, amount); - } -}; - -SpriteBatchRenderer.prototype.uploadAlpha = function (children) -{ - var j = 0; - for (var i = 0; i < children.length; i+=this.size) - { - var amount = ( children.length - i ); - if(amount > this.size) - { - amount = this.size; - } - - this.buffers[j++].uploadAlpha(children, i, amount); - } -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteBatchRenderer.prototype.flush = function () -{ - // If the batch is length 0 then return as there is nothing to draw - if (this.currentBatchSize === 0) - { - return; - } - - var nextTexture; - var batchSize = 0; - var start = 0; - - var currentBaseTexture = null; - - var sprite; - - for (var i = 0, j = this.currentBatchSize; i < j; i++) - { - - sprite = this.sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if (currentBaseTexture !== nextTexture) - { - this.renderBatch(currentBaseTexture, batchSize, start); - - start = i; - batchSize = 0; - currentBaseTexture = nextTexture; - } - - batchSize++; - } - - this.renderBatch(currentBaseTexture, batchSize, start); - - // then reset the batch! - this.currentBatchSize = 0; -}; - -/** - * Draws the currently batches sprites. - * - * @private - * @param texture {Texture} - * @param size {number} - * @param startIndex {number} - */ -SpriteBatchRenderer.prototype.renderBatch = function (texture, size, startIndex) -{ - if (size === 0) - { - return; - } - - var gl = this.renderer.gl; - - if (!texture._glTextures[gl.id]) - { - this.renderer.updateTexture(texture); - } - else - { - // bind the current texture - gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); - } - - var j = 0; - for (var i = startIndex; i < size; i+=this.size) { - - this.buffers[j++].bind( this.shader ); - - - var amount = ( size - i) ; - if(amount > this.size) - { - amount = this.size; - } - - // now draw those suckas! - gl.drawElements(gl.TRIANGLES, this.size * 6, gl.UNSIGNED_SHORT, 0);//(startIndex % this.size) * 6 * 2); - } - - - - // increment the draw count - this.renderer.drawCount++; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteBatchRenderer.prototype.start = function () -{ - var gl = this.renderer.gl; - - // bind the main texture - gl.activeTexture(gl.TEXTURE0); - - // bind the buffers - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - var shader = this.shader; - - this.renderer.shaderManager.setShader(shader); -}; - -/** - * Destroys the SpriteBatch. - * - */ -SpriteBatchRenderer.prototype.destroy = function () -{ - this.renderer.gl.deleteBuffer(this.vertexBuffer); - this.renderer.gl.deleteBuffer(this.indexBuffer); - - this.shader.destroy(); - - this.renderer = null; - - this.vertices = null; - this.indices = null; - - this.vertexBuffer = null; - this.indexBuffer = null; - - this.drawing = false; - - this.textures = null; - this.blendModes = null; - this.shaders = null; - this.sprites = null; - this.shader = null; -}; - -var SpriteDataCache = function () -{ - this.rotation = 0; - this.pX = 0; - this.pY = 0; - this.texture = null; - this.sX = 0; - this.sY = 0; - this.alpha = 0; -} - diff --git a/src/core/index.js b/src/core/index.js index d533cf9..e373407 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -24,9 +24,9 @@ DisplayObjectContainer: require('./display/Container'), Sprite: require('./sprites/Sprite'), - SpriteBatch: require('./sprites/SpriteBatch'), + ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), - SpriteBatchRenderer: require('./sprites/webgl/SpriteBatchRenderer'), + ParticleRenderer: require('./particles/webgl/ParticleRenderer'), // primitives Graphics: require('./graphics/Graphics'), diff --git a/src/core/particles/ParticleContainer.js b/src/core/particles/ParticleContainer.js new file mode 100644 index 0000000..a7eef53 --- /dev/null +++ b/src/core/particles/ParticleContainer.js @@ -0,0 +1,176 @@ +var Container = require('../display/Container'); + +/** + * The Particle class is a really fast version of the Container built solely for speed, + * so use when you need a lot of sprites or particles. The tradeoff of the Particle is that advanced + * functionality will not work. Particle implements only the basic object transform (position, scale, rotation). + * Any other functionality like tinting, masking, etc will not work on sprites in this batch. + * + * It's extremely easy to use : + * + * ```js + * var container = new Particle(); + * + * for(var i = 0; i < 100; ++i) + * { + * var sprite = new PIXI.Sprite.fromImage("myImage.png"); + * container.addChild(sprite); + * } + * ``` + * + * And here you have a hundred sprites that will be renderer at the speed of light. + * + * @class + * @namespace PIXI + */ + +//TODO RENAME to PARTICLE CONTAINER? +function Particle() +{ + Container.call(this); +} + +Particle.prototype = Object.create(Container.prototype); +Particle.prototype.constructor = Particle; +module.exports = Particle; + +/** + * Updates the object transform for rendering + * + * @private + */ +Particle.prototype.updateTransform = function () +{ + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} The webgl renderer + * @private + */ +Particle.prototype.renderWebGL = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} The canvas renderer + * @private + */ +Particle.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; + } + + var frame = child.texture.frame; + + context.globalAlpha = this.worldAlpha * child.alpha; + + if (child.rotation % (Math.PI * 2) === 0) + { + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx, + transform.ty + ); + + isRotated = false; + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, + ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, + frame.width * child.scale.x, + frame.height * child.scale.y + ); + } + else + { + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx | 0, + childTransform.ty | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx, + childTransform.ty + ); + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width) + 0.5) | 0, + ((child.anchor.y) * (-frame.height) + 0.5) | 0, + frame.width, + frame.height + ); + } + } +}; diff --git a/src/core/particles/webgl/ParticleBuffer.js b/src/core/particles/webgl/ParticleBuffer.js new file mode 100644 index 0000000..0e82ad4 --- /dev/null +++ b/src/core/particles/webgl/ParticleBuffer.js @@ -0,0 +1,317 @@ + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi 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 + */ + +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this sprite batch works for. + */ +function ParticleBuffer(gl, size ) +{ + this.gl = gl; + + /** + * + * + * @member {number} + */ + this.vertSize = 2; + + /** + * + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = size; + + // the total number of bytes in our batch + var numVerts = this.size * 2 * this.vertByteSize; + + /** + * Holds the vertices + * + * @member {ArrayBuffer} + */ + this.vertices = new Float32Array(numVerts); + this.position = new Float32Array(numVerts); + this.rotation = new Float32Array(numVerts/2); + this.uvs = new Float32Array(numVerts); + this.alpha = new Float32Array(numVerts/2); + + this.initBuffers(); + + +} + +ParticleBuffer.prototype.constructor = ParticleBuffer; +module.exports = ParticleBuffer; + +/** + * Sets up the renderer context and necessary buffers. + * + * @private + * @param gl {WebGLContext} the current WebGL drawing context + */ +ParticleBuffer.prototype.initBuffers = function () +{ + var gl = this.gl; + + // create a couple of buffers + this.vertexBuffer = gl.createBuffer(); + this.positionBuffer = gl.createBuffer(); + this.rotationBuffer = gl.createBuffer(); + this.uvsBuffer = gl.createBuffer(); + this.alphaBuffer = gl.createBuffer(); + + // upload the buffers.. + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.position, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.DYNAMIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.alpha, gl.DYNAMIC_DRAW); +}; + + +ParticleBuffer.prototype.refresh = function(children, startIndex, amount) +{ + this.uploadVerticies(children,startIndex, amount); + this.uploadRotation(children,startIndex, amount); + this.uploadUvs(children,startIndex, amount); + this.uploadAlpha(children,startIndex, amount); + +}; + +ParticleBuffer.prototype.uploadVerticies = function (children,startIndex, amount) +{ + var vertices = this.vertices, + index = 0, + sprite, + texture, + trim, + sx, + sy, + w0, w1, h0, h1; + + for (var i = 0; i < amount; i++) { + + sprite = children[startIndex + i]; + texture = sprite._texture; + sx = sprite.scale.x; + sy = sprite.scale.y; + + if (texture.trim) + { + // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. + trim = texture.trim; + + w1 = trim.x - sprite.anchor.x * trim.width; + w0 = w1 + texture.crop.width; + + h1 = trim.y - sprite.anchor.y * trim.height; + h0 = h1 + texture.crop.height; + } + else + { + w0 = (texture._frame.width ) * (1-sprite.anchor.x); + w1 = (texture._frame.width ) * -sprite.anchor.x; + + h0 = texture._frame.height * (1-sprite.anchor.y); + h1 = texture._frame.height * -sprite.anchor.y; + } + + vertices[index++] = w1 * sx; + vertices[index++] = h1 * sy; + + vertices[index++] = w0 * sx; + vertices[index++] = h1 * sy; + + vertices[index++] = w0 * sx; + vertices[index++] = h0 * sy; + + vertices[index++] = w1 * sx; + vertices[index++] = h0 * sy; + } + + this.uploadToBuffer(this.vertexBuffer, this.vertices, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadPosition = function (children,startIndex, amount) +{ + var position = this.position, + index = 0, + spritePosition; + + for (var i = 0; i < amount; i++) { + + spritePosition = children[startIndex + i].position; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + + position[index++] = spritePosition.x; + position[index++] = spritePosition.y; + } + + + // upload the verts to the buffer + this.uploadToBuffer(this.positionBuffer, this.position, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadRotation = function (children,startIndex, amount) +{ + var rotation = this.rotation, + index = 0, + spriteRotation; + + for (var i = 0; i < amount; i++) { + + spriteRotation = children[startIndex + i].rotation; + + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + rotation[index++] = spriteRotation; + } + + this.uploadToBuffer(this.rotationBuffer, this.rotation, 1 * 4, amount); +}; + +ParticleBuffer.prototype.uploadUvs = function (children,startIndex, amount) +{ + var uvs = this.uvs, + index = 0, + textureUvs; + + for (var i = 0; i < amount; i++) { + + textureUvs = children[startIndex + i]._texture._uvs; + + if(textureUvs) + { + uvs[index++] = textureUvs.x0; + uvs[index++] = textureUvs.y0; + + uvs[index++] = textureUvs.x1; + uvs[index++] = textureUvs.y1; + + uvs[index++] = textureUvs.x2; + uvs[index++] = textureUvs.y2; + + uvs[index++] = textureUvs.x3; + uvs[index++] = textureUvs.y3; + } + else + { + index += 8; + } + } + + this.uploadToBuffer(this.uvsBuffer, this.uvs, 2 * 4, amount); +}; + +ParticleBuffer.prototype.uploadAlpha = function (children,startIndex, amount) +{ + var alpha = this.alpha, + index = 0, + spriteAlpha; + + for (var i = 0; i < amount; i++) { + + spriteAlpha = children[startIndex + i].alpha; + + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + alpha[index++] = spriteAlpha; + } + + + this.uploadToBuffer(this.alphaBuffer, this.alpha, 1 * 4, amount); + +}; + +ParticleBuffer.prototype.uploadToBuffer = function (buffer, data, dataSize, amount) +{ + var gl = this.gl; + + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + + if (this.currentBatchSize > ( this.size * 0.5 ) ) + { + gl.bufferSubData(gl.ARRAY_BUFFER, 0, data); + } + else + { + var view = data.subarray(0, amount * dataSize); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); + } +}; + +/** + * Starts a new sprite batch. + * + */ +ParticleBuffer.prototype.bind = function (shader) +{ + var gl = this.gl; + + // this is the same for each shader? + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); + gl.vertexAttribPointer(shader.attributes.aPositionCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); + gl.vertexAttribPointer(shader.attributes.aRotation, 1, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); + gl.vertexAttribPointer(shader.attributes.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); + gl.vertexAttribPointer(shader.attributes.aColor, 1, gl.FLOAT, false, 0, 0); + +}; + +/** + * Destroys the SpriteBatch. + * + */ +ParticleBuffer.prototype.destroy = function () +{ + //TODO implement this :) to busy making the fun bits.. +}; diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js new file mode 100644 index 0000000..29ed7c7 --- /dev/null +++ b/src/core/particles/webgl/ParticleRenderer.js @@ -0,0 +1,464 @@ +var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), + WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), + ParticleShader = require('./ParticleShader'), + ParticleBuffer = require('./ParticleBuffer'), + math = require('../../math'); + +var SpriteDataCache = function () +{ + this.rotation = 0; + this.pX = 0; + this.pY = 0; + this.texture = null; + this.sX = 0; + this.sY = 0; + this.alpha = 0; +}; + + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi 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 ParticleRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleRenderer.java + */ + +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this sprite batch works for. + */ +function ParticleRenderer(renderer) +{ + ObjectRenderer.call(this, renderer); + + + /** + * The number of images in the Particle before it flushes. + * + * @member {number} + */ + this.size = 15000;//CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + this.maxSprites = 200000; + + var numIndices = this.size * 6; + + /** + * Holds the indices + * + * @member {Uint16Array} + */ + this.indices = new Uint16Array(numIndices); + + 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; + } + + /** + * + * + * @member {number} + */ + this.currentBatchSize = 0; + + /** + * + * + * @member {Array} + */ + this.sprites = []; + this.spriteDataCache = []; + + /** + * The default shader that is used if a sprite doesn't have a more specific one. + * + * @member {Shader} + */ + this.shader = null; + + this.buffers = []; + + this.tempMatrix = new math.Matrix(); +} + +ParticleRenderer.prototype = Object.create(ObjectRenderer.prototype); +ParticleRenderer.prototype.constructor = ParticleRenderer; +module.exports = ParticleRenderer; + +WebGLRenderer.registerPlugin('particle', ParticleRenderer); + +/** + * Sets up the renderer context and necessary buffers. + * + * @private + * @param gl {WebGLContext} the current WebGL drawing context + */ +ParticleRenderer.prototype.onContextChange = function () +{ + var gl = this.renderer.gl; + + // setup default shader + this.shader = new ParticleShader(this.renderer.shaderManager); + + 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); + + this.setSize(this.maxSprites); +}; + +ParticleRenderer.prototype.setSize = function ( totalSize ) +{ + var gl = this.renderer.gl; + + var i; + + for (i = 0; i < totalSize; i += this.size) + { + this.buffers.push( new ParticleBuffer(gl, this.size) ); + } + + for (i = 0; i < totalSize; i++) + { + this.spriteDataCache.push(new SpriteDataCache()); + } +}; + + +/** + * Renders the sprite object. + * + * @param sprite {Sprite} the sprite to render when using this Particle + */ +ParticleRenderer.prototype.render = function ( Particle ) +{ + var children = Particle.children; + + // if the uvs have not updated then no point rendering just yet! + //this.renderer.blendModeManager.setBlendMode(sprite.blendMode); + var gl = this.renderer.gl; + + var m = Particle.worldTransform.copy( this.tempMatrix ); + m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); + + gl.uniformMatrix3fv(this.shader.uniforms.projectionMatrix._location, false, m.toArray(true)); + + this.currentBatchSize = 0; +/* + var rotationDirty = false; + var positionDirty = false; + var vertsDirty = false; + var uvsDirty = false; + var alphaDirty = false;*/ + + this.sprites = children; + + this.vertDirty = 0; + + for (var i=0,j= children.length; i this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadVerticies(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadPosition = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+= this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadPosition(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadRotation = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadRotation(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadUvs = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadUvs(children, i, amount); + } +}; + +ParticleRenderer.prototype.uploadAlpha = function (children) +{ + var j = 0; + for (var i = 0; i < children.length; i+=this.size) + { + var amount = ( children.length - i ); + if(amount > this.size) + { + amount = this.size; + } + + this.buffers[j++].uploadAlpha(children, i, amount); + } +}; + +/** + * Renders the content and empties the current batch. + * + */ +ParticleRenderer.prototype.flush = function () +{ + // If the batch is length 0 then return as there is nothing to draw + if (this.currentBatchSize === 0) + { + return; + } + + var nextTexture; + var batchSize = 0; + var start = 0; + + var currentBaseTexture = null; + + var sprite; + + for (var i = 0, j = this.currentBatchSize; i < j; i++) + { + + sprite = this.sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if (currentBaseTexture !== nextTexture) + { + this.renderBatch(currentBaseTexture, batchSize, start); + + start = i; + batchSize = 0; + currentBaseTexture = nextTexture; + } + + batchSize++; + } + + this.renderBatch(currentBaseTexture, batchSize, start); + + // then reset the batch! + this.currentBatchSize = 0; +}; + +/** + * Draws the currently batches sprites. + * + * @private + * @param texture {Texture} + * @param size {number} + * @param startIndex {number} + */ +ParticleRenderer.prototype.renderBatch = function (texture, size, startIndex) +{ + if (size === 0) + { + return; + } + + var gl = this.renderer.gl; + + if (!texture._glTextures[gl.id]) + { + this.renderer.updateTexture(texture); + } + else + { + // bind the current texture + gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); + } + + var j = 0; + for (var i = startIndex; i < size; i+=this.size) { + + this.buffers[j++].bind( this.shader ); + + + var amount = ( size - i) ; + if(amount > this.size) + { + amount = this.size; + } + + // now draw those suckas! + gl.drawElements(gl.TRIANGLES, this.size * 6, gl.UNSIGNED_SHORT, 0);//(startIndex % this.size) * 6 * 2); + } + + + + // increment the draw count + this.renderer.drawCount++; +}; + +/** + * Starts a new sprite batch. + * + */ +ParticleRenderer.prototype.start = function () +{ + var gl = this.renderer.gl; + + // bind the main texture + gl.activeTexture(gl.TEXTURE0); + + // bind the buffers + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + var shader = this.shader; + + this.renderer.shaderManager.setShader(shader); +}; + +/** + * Destroys the Particle. + * + */ +ParticleRenderer.prototype.destroy = function () +{ + this.renderer.gl.deleteBuffer(this.vertexBuffer); + this.renderer.gl.deleteBuffer(this.indexBuffer); + + this.shader.destroy(); + + this.renderer = null; + + this.vertices = null; + this.indices = null; + + this.vertexBuffer = null; + this.indexBuffer = null; + + this.drawing = false; + + this.textures = null; + this.blendModes = null; + this.shaders = null; + this.sprites = null; + this.shader = null; +}; + + diff --git a/src/core/particles/webgl/ParticleShader.js b/src/core/particles/webgl/ParticleShader.js new file mode 100644 index 0000000..97836c2 --- /dev/null +++ b/src/core/particles/webgl/ParticleShader.js @@ -0,0 +1,71 @@ +var TextureShader = require('../../renderers/webgl/shaders/TextureShader'); + +/** + * @class + * @extends Shader + * @namespace PIXI + * @param shaderManager {ShaderManager} The webgl shader manager this shader works for. + */ +function ParticleShader(shaderManager) +{ + TextureShader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + 'attribute float aColor;', + + 'attribute vec2 aPositionCoord;', + 'attribute vec2 aScale;', + 'attribute float aRotation;', + + 'uniform mat3 projectionMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + + 'void main(void){', + ' vec2 v = aVertexPosition;', + + ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', + ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', + ' v = v + aPositionCoord;', + + ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', + + ' vTextureCoord = aTextureCoord;', + ' vColor = aColor;', + '}' + ].join('\n'), + // hello + [ + 'precision lowp float;', + + 'varying vec2 vTextureCoord;', + 'varying float vColor;', + + 'uniform sampler2D uSampler;', + + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', + '}' + ].join('\n'), + // custom uniforms + null, + // custom attributes + { + aPositionCoord: 0, + // aScale: 0, + aRotation: 0 + } + ); + + // TEMP HACK + +} + +ParticleShader.prototype = Object.create(TextureShader.prototype); +ParticleShader.prototype.constructor = ParticleShader; + +module.exports = ParticleShader; diff --git a/src/core/sprites/SpriteBatch.js b/src/core/sprites/SpriteBatch.js deleted file mode 100644 index 4238b4c..0000000 --- a/src/core/sprites/SpriteBatch.js +++ /dev/null @@ -1,180 +0,0 @@ -var Container = require('../display/Container'); - -/** - * The SpriteBatch class is a really fast version of the Container built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced - * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new SpriteBatch(); - * - * for(var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @namespace PIXI - */ - -//TODO RENAME to PARTICLE CONTAINER? -function SpriteBatch() -{ - Container.call(this); -} - -SpriteBatch.prototype = Object.create(Container.prototype); -SpriteBatch.prototype.constructor = SpriteBatch; -module.exports = SpriteBatch; - -/** - * Updates the object transform for rendering - * - * @private - */ -SpriteBatch.prototype.updateTransform = function () -{ - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} The webgl renderer - * @private - */ -SpriteBatch.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - renderer.setObjectRenderer( renderer.plugins.spriteBatch ); - - - - renderer.plugins.spriteBatch.render( this ); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} The canvas renderer - * @private - */ -SpriteBatch.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, - ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, - frame.width * child.scale.x, - frame.height * child.scale.y - ); - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width) + 0.5) | 0, - ((child.anchor.y) * (-frame.height) + 0.5) | 0, - frame.width, - frame.height - ); - } - } -}; diff --git a/src/core/sprites/webgl/SpriteBatchBuffers.js b/src/core/sprites/webgl/SpriteBatchBuffers.js deleted file mode 100644 index e28daf5..0000000 --- a/src/core/sprites/webgl/SpriteBatchBuffers.js +++ /dev/null @@ -1,317 +0,0 @@ - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ - * for creating the original pixi 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 SpriteBatchBuffers: - * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/SpriteBatchBuffers.java - */ - -/** - * - * @class - * @private - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this sprite batch works for. - */ -function SpriteBatchBuffers(gl, size ) -{ - this.gl = gl; - - /** - * - * - * @member {number} - */ - this.vertSize = 2; - - /** - * - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = size; - - // the total number of bytes in our batch - var numVerts = this.size * 2 * this.vertByteSize; - - /** - * Holds the vertices - * - * @member {ArrayBuffer} - */ - this.vertices = new Float32Array(numVerts); - this.position = new Float32Array(numVerts); - this.rotation = new Float32Array(numVerts/2); - this.uvs = new Float32Array(numVerts); - this.alpha = new Float32Array(numVerts/2); - - this.initBuffers(); - - -} - -SpriteBatchBuffers.prototype.constructor = SpriteBatchBuffers; -module.exports = SpriteBatchBuffers; - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - * @param gl {WebGLContext} the current WebGL drawing context - */ -SpriteBatchBuffers.prototype.initBuffers = function () -{ - var gl = this.gl; - - // create a couple of buffers - this.vertexBuffer = gl.createBuffer(); - this.positionBuffer = gl.createBuffer(); - this.rotationBuffer = gl.createBuffer(); - this.uvsBuffer = gl.createBuffer(); - this.alphaBuffer = gl.createBuffer(); - - // upload the buffers.. - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.position, gl.DYNAMIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.DYNAMIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.alpha, gl.DYNAMIC_DRAW); -}; - - -SpriteBatchBuffers.prototype.refresh = function(children, startIndex, amount) -{ - this.uploadVerticies(children,startIndex, amount); - this.uploadRotation(children,startIndex, amount); - this.uploadUvs(children,startIndex, amount); - this.uploadAlpha(children,startIndex, amount); - -}; - -SpriteBatchBuffers.prototype.uploadVerticies = function (children,startIndex, amount) -{ - var vertices = this.vertices, - index = 0, - sprite, - texture, - trim, - sx, - sy, - w0, w1, h0, h1; - - for (var i = 0; i < amount; i++) { - - sprite = children[startIndex + i]; - texture = sprite._texture; - sx = sprite.scale.x; - sy = sprite.scale.y; - - if (texture.trim) - { - // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. - trim = texture.trim; - - w1 = trim.x - sprite.anchor.x * trim.width; - w0 = w1 + texture.crop.width; - - h1 = trim.y - sprite.anchor.y * trim.height; - h0 = h1 + texture.crop.height; - } - else - { - w0 = (texture._frame.width ) * (1-sprite.anchor.x); - w1 = (texture._frame.width ) * -sprite.anchor.x; - - h0 = texture._frame.height * (1-sprite.anchor.y); - h1 = texture._frame.height * -sprite.anchor.y; - } - - vertices[index++] = w1 * sx; - vertices[index++] = h1 * sy; - - vertices[index++] = w0 * sx; - vertices[index++] = h1 * sy; - - vertices[index++] = w0 * sx; - vertices[index++] = h0 * sy; - - vertices[index++] = w1 * sx; - vertices[index++] = h0 * sy; - } - - this.uploadToBuffer(this.vertexBuffer, this.vertices, 2 * 4, amount); -}; - -SpriteBatchBuffers.prototype.uploadPosition = function (children,startIndex, amount) -{ - var position = this.position, - index = 0, - spritePosition; - - for (var i = 0; i < amount; i++) { - - spritePosition = children[startIndex + i].position; - - position[index++] = spritePosition.x; - position[index++] = spritePosition.y; - - position[index++] = spritePosition.x; - position[index++] = spritePosition.y; - - position[index++] = spritePosition.x; - position[index++] = spritePosition.y; - - position[index++] = spritePosition.x; - position[index++] = spritePosition.y; - } - - - // upload the verts to the buffer - this.uploadToBuffer(this.positionBuffer, this.position, 2 * 4, amount); -}; - -SpriteBatchBuffers.prototype.uploadRotation = function (children,startIndex, amount) -{ - var rotation = this.rotation, - index = 0, - spriteRotation; - - for (var i = 0; i < amount; i++) { - - spriteRotation = children[startIndex + i].rotation; - - rotation[index++] = spriteRotation; - rotation[index++] = spriteRotation; - rotation[index++] = spriteRotation; - rotation[index++] = spriteRotation; - } - - this.uploadToBuffer(this.rotationBuffer, this.rotation, 1 * 4, amount); -}; - -SpriteBatchBuffers.prototype.uploadUvs = function (children,startIndex, amount) -{ - var uvs = this.uvs, - index = 0, - textureUvs; - - for (var i = 0; i < amount; i++) { - - textureUvs = children[startIndex + i]._texture._uvs; - - if(textureUvs) - { - uvs[index++] = textureUvs.x0; - uvs[index++] = textureUvs.y0; - - uvs[index++] = textureUvs.x1; - uvs[index++] = textureUvs.y1; - - uvs[index++] = textureUvs.x2; - uvs[index++] = textureUvs.y2; - - uvs[index++] = textureUvs.x3; - uvs[index++] = textureUvs.y3; - } - else - { - index += 8; - } - } - - this.uploadToBuffer(this.uvsBuffer, this.uvs, 2 * 4, amount); -}; - -SpriteBatchBuffers.prototype.uploadAlpha = function (children,startIndex, amount) -{ - var alpha = this.alpha, - index = 0, - spriteAlpha; - - for (var i = 0; i < amount; i++) { - - spriteAlpha = children[startIndex + i].alpha; - - alpha[index++] = spriteAlpha; - alpha[index++] = spriteAlpha; - alpha[index++] = spriteAlpha; - alpha[index++] = spriteAlpha; - } - - - this.uploadToBuffer(this.alphaBuffer, this.alpha, 1 * 4, amount); - -}; - -SpriteBatchBuffers.prototype.uploadToBuffer = function (buffer, data, dataSize, amount) -{ - var gl = this.gl; - - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - - if (this.currentBatchSize > ( this.size * 0.5 ) ) - { - gl.bufferSubData(gl.ARRAY_BUFFER, 0, data); - } - else - { - var view = data.subarray(0, amount * dataSize); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); - } -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteBatchBuffers.prototype.bind = function (shader) -{ - var gl = this.gl; - - // this is the same for each shader? - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); - gl.vertexAttribPointer(shader.attributes.aPositionCoord, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.rotationBuffer); - gl.vertexAttribPointer(shader.attributes.aRotation, 1, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvsBuffer); - gl.vertexAttribPointer(shader.attributes.aTextureCoord, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.alphaBuffer); - gl.vertexAttribPointer(shader.attributes.aColor, 1, gl.FLOAT, false, 0, 0); - -}; - -/** - * Destroys the SpriteBatch. - * - */ -SpriteBatchBuffers.prototype.destroy = function () -{ - //TODO implement this :) to busy making the fun bits.. -}; diff --git a/src/core/sprites/webgl/SpriteBatchRenderer.js b/src/core/sprites/webgl/SpriteBatchRenderer.js deleted file mode 100644 index e37b573..0000000 --- a/src/core/sprites/webgl/SpriteBatchRenderer.js +++ /dev/null @@ -1,459 +0,0 @@ -var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), - WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), - SpriteBatchShader = require('./SpriteBatchShader'), - SpriteBatchBuffers = require('./SpriteBatchBuffers'), - math = require('../../math'); - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ - * for creating the original pixi 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 SpriteBatchRenderer: - * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/SpriteBatchRenderer.java - */ - -/** - * - * @class - * @private - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this sprite batch works for. - */ -function SpriteBatchRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = 15000;//CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - this.maxSprites = 200000; - - var numIndices = this.size * 6; - - /** - * Holds the indices - * - * @member {Uint16Array} - */ - this.indices = new Uint16Array(numIndices); - - 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; - } - - /** - * - * - * @member {number} - */ - this.currentBatchSize = 0; - - /** - * - * - * @member {Array} - */ - this.sprites = []; - this.spriteDataCache = []; - - /** - * The default shader that is used if a sprite doesn't have a more specific one. - * - * @member {Shader} - */ - this.shader = null; - - this.buffers = []; - - this.tempMatrix = new math.Matrix(); -} - -SpriteBatchRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteBatchRenderer.prototype.constructor = SpriteBatchRenderer; -module.exports = SpriteBatchRenderer; - -WebGLRenderer.registerPlugin('spriteBatch', SpriteBatchRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - * @param gl {WebGLContext} the current WebGL drawing context - */ -SpriteBatchRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // setup default shader - this.shader = new SpriteBatchShader(this.renderer.shaderManager); - - 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); - - this.setSize(this.maxSprites); -}; - -SpriteBatchRenderer.prototype.setSize = function ( totalSize ) -{ - var gl = this.renderer.gl; - - for (var i = 0; i < totalSize; i += this.size) - { - this.buffers.push( new SpriteBatchBuffers(gl, this.size) ); - } - - for (var i = 0; i < totalSize; i++) - { - this.spriteDataCache.push(new SpriteDataCache()); - }; -}; - - -/** - * Renders the sprite object. - * - * @param sprite {Sprite} the sprite to render when using this spritebatch - */ -SpriteBatchRenderer.prototype.render = function ( spriteBatch ) -{ - var children = spriteBatch.children; - - // if the uvs have not updated then no point rendering just yet! - //this.renderer.blendModeManager.setBlendMode(sprite.blendMode); - var gl = this.renderer.gl; - - var m = spriteBatch.worldTransform.copy( this.tempMatrix ); - m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); - - gl.uniformMatrix3fv(this.shader.uniforms.projectionMatrix._location, false, m.toArray(true)); - - this.currentBatchSize = 0; - - var rotationDirty = false; - var positionDirty = false; - var vertsDirty = false; - var uvsDirty = false; - var alphaDirty = false; - - this.sprites = children; - - this.vertDirty = 0; - - for (var i=0,j= children.length; i this.size) - { - amount = this.size; - } - - this.buffers[j++].uploadVerticies(children, i, amount); - } -}; - -SpriteBatchRenderer.prototype.uploadPosition = function (children) -{ - var j = 0; - for (var i = 0; i < children.length; i+= this.size) - { - var amount = ( children.length - i ); - if(amount > this.size) - { - amount = this.size; - } - - this.buffers[j++].uploadPosition(children, i, amount); - } -}; - -SpriteBatchRenderer.prototype.uploadRotation = function (children) -{ - var j = 0; - for (var i = 0; i < children.length; i+=this.size) - { - var amount = ( children.length - i ); - if(amount > this.size) - { - amount = this.size; - } - - this.buffers[j++].uploadRotation(children, i, amount); - } -}; - -SpriteBatchRenderer.prototype.uploadUvs = function (children) -{ - var j = 0; - for (var i = 0; i < children.length; i+=this.size) - { - var amount = ( children.length - i ); - if(amount > this.size) - { - amount = this.size; - } - - this.buffers[j++].uploadUvs(children, i, amount); - } -}; - -SpriteBatchRenderer.prototype.uploadAlpha = function (children) -{ - var j = 0; - for (var i = 0; i < children.length; i+=this.size) - { - var amount = ( children.length - i ); - if(amount > this.size) - { - amount = this.size; - } - - this.buffers[j++].uploadAlpha(children, i, amount); - } -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteBatchRenderer.prototype.flush = function () -{ - // If the batch is length 0 then return as there is nothing to draw - if (this.currentBatchSize === 0) - { - return; - } - - var nextTexture; - var batchSize = 0; - var start = 0; - - var currentBaseTexture = null; - - var sprite; - - for (var i = 0, j = this.currentBatchSize; i < j; i++) - { - - sprite = this.sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if (currentBaseTexture !== nextTexture) - { - this.renderBatch(currentBaseTexture, batchSize, start); - - start = i; - batchSize = 0; - currentBaseTexture = nextTexture; - } - - batchSize++; - } - - this.renderBatch(currentBaseTexture, batchSize, start); - - // then reset the batch! - this.currentBatchSize = 0; -}; - -/** - * Draws the currently batches sprites. - * - * @private - * @param texture {Texture} - * @param size {number} - * @param startIndex {number} - */ -SpriteBatchRenderer.prototype.renderBatch = function (texture, size, startIndex) -{ - if (size === 0) - { - return; - } - - var gl = this.renderer.gl; - - if (!texture._glTextures[gl.id]) - { - this.renderer.updateTexture(texture); - } - else - { - // bind the current texture - gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); - } - - var j = 0; - for (var i = startIndex; i < size; i+=this.size) { - - this.buffers[j++].bind( this.shader ); - - - var amount = ( size - i) ; - if(amount > this.size) - { - amount = this.size; - } - - // now draw those suckas! - gl.drawElements(gl.TRIANGLES, this.size * 6, gl.UNSIGNED_SHORT, 0);//(startIndex % this.size) * 6 * 2); - } - - - - // increment the draw count - this.renderer.drawCount++; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteBatchRenderer.prototype.start = function () -{ - var gl = this.renderer.gl; - - // bind the main texture - gl.activeTexture(gl.TEXTURE0); - - // bind the buffers - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - var shader = this.shader; - - this.renderer.shaderManager.setShader(shader); -}; - -/** - * Destroys the SpriteBatch. - * - */ -SpriteBatchRenderer.prototype.destroy = function () -{ - this.renderer.gl.deleteBuffer(this.vertexBuffer); - this.renderer.gl.deleteBuffer(this.indexBuffer); - - this.shader.destroy(); - - this.renderer = null; - - this.vertices = null; - this.indices = null; - - this.vertexBuffer = null; - this.indexBuffer = null; - - this.drawing = false; - - this.textures = null; - this.blendModes = null; - this.shaders = null; - this.sprites = null; - this.shader = null; -}; - -var SpriteDataCache = function () -{ - this.rotation = 0; - this.pX = 0; - this.pY = 0; - this.texture = null; - this.sX = 0; - this.sY = 0; - this.alpha = 0; -} - diff --git a/src/core/sprites/webgl/SpriteBatchShader.js b/src/core/sprites/webgl/SpriteBatchShader.js deleted file mode 100644 index 81d9a6f..0000000 --- a/src/core/sprites/webgl/SpriteBatchShader.js +++ /dev/null @@ -1,71 +0,0 @@ -var TextureShader = require('../../renderers/webgl/shaders/TextureShader'); - -/** - * @class - * @extends Shader - * @namespace PIXI - * @param shaderManager {ShaderManager} The webgl shader manager this shader works for. - */ -function SpriteBatchShader(shaderManager) -{ - TextureShader.call(this, - shaderManager, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - 'attribute float aColor;', - - 'attribute vec2 aPositionCoord;', - 'attribute vec2 aScale;', - 'attribute float aRotation;', - - 'uniform mat3 projectionMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying float vColor;', - - 'void main(void){', - ' vec2 v = aVertexPosition;', - - ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', - ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', - ' v = v + aPositionCoord;', - - ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', - - ' vTextureCoord = aTextureCoord;', - ' vColor = aColor;', - '}' - ].join('\n'), - // hello - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying float vColor;', - - 'uniform sampler2D uSampler;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', - '}' - ].join('\n'), - // custom uniforms - null, - // custom attributes - { - aPositionCoord: 0, - // aScale: 0, - aRotation: 0 - } - ); - - // TEMP HACK - -} - -SpriteBatchShader.prototype = Object.create(TextureShader.prototype); -SpriteBatchShader.prototype.constructor = SpriteBatchShader; - -module.exports = SpriteBatchShader;