'use strict';
exports.__esModule = true;
var _ObjectRenderer2 = require('../../renderers/webgl/utils/ObjectRenderer');
var _ObjectRenderer3 = _interopRequireDefault(_ObjectRenderer2);
var _WebGLRenderer = require('../../renderers/webgl/WebGLRenderer');
var _WebGLRenderer2 = _interopRequireDefault(_WebGLRenderer);
var _createIndicesForQuads = require('../../utils/createIndicesForQuads');
var _createIndicesForQuads2 = _interopRequireDefault(_createIndicesForQuads);
var _generateMultiTextureShader = require('./generateMultiTextureShader');
var _generateMultiTextureShader2 = _interopRequireDefault(_generateMultiTextureShader);
var _checkMaxIfStatmentsInShader = require('../../renderers/webgl/utils/checkMaxIfStatmentsInShader');
var _checkMaxIfStatmentsInShader2 = _interopRequireDefault(_checkMaxIfStatmentsInShader);
var _BatchBuffer = require('./BatchBuffer');
var _BatchBuffer2 = _interopRequireDefault(_BatchBuffer);
var _settings = require('../../settings');
var _settings2 = _interopRequireDefault(_settings);
var _pixiGlCore = require('pixi-gl-core');
var _pixiGlCore2 = _interopRequireDefault(_pixiGlCore);
var _bitTwiddle = require('bit-twiddle');
var _bitTwiddle2 = _interopRequireDefault(_bitTwiddle);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var TICK = 0;
var TEXTURE_TICK = 0;
/**
* Renderer dedicated to drawing and batching sprites.
*
* @class
* @private
* @memberof PIXI
* @extends PIXI.ObjectRenderer
*/
var SpriteRenderer = function (_ObjectRenderer) {
_inherits(SpriteRenderer, _ObjectRenderer);
/**
* @param {PIXI.WebGLRenderer} renderer - The renderer this sprite batch works for.
*/
function SpriteRenderer(renderer) {
_classCallCheck(this, SpriteRenderer);
/**
* Number of values sent in the vertex buffer.
* positionX, positionY, colorR, colorG, colorB = 5
*
* @member {number}
*/
var _this = _possibleConstructorReturn(this, _ObjectRenderer.call(this, renderer));
_this.vertSize = 5;
/**
* The size of the vertex information in bytes.
*
* @member {number}
*/
_this.vertByteSize = _this.vertSize * 4;
/**
* The number of images in the SpriteRenderer before it flushes.
*
* @member {number}
*/
_this.size = _settings2.default.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop
// the total number of bytes in our batch
// let numVerts = this.size * 4 * this.vertByteSize;
_this.buffers = [];
for (var i = 1; i <= _bitTwiddle2.default.nextPow2(_this.size); i *= 2) {
_this.buffers.push(new _BatchBuffer2.default(i * 4 * _this.vertByteSize));
}
/**
* Holds the indices of the geometry (quads) to draw
*
* @member {Uint16Array}
*/
_this.indices = (0, _createIndicesForQuads2.default)(_this.size);
/**
* The default shaders that is used if a sprite doesn't have a more specific one.
* there is a shader for each number of textures that can be rendererd.
* These shaders will also be generated on the fly as required.
* @member {PIXI.Shader[]}
*/
_this.shader = null;
_this.currentIndex = 0;
TICK = 0;
_this.groups = [];
for (var k = 0; k < _this.size; k++) {
_this.groups[k] = { textures: [], textureCount: 0, ids: [], size: 0, start: 0, blend: 0 };
}
_this.sprites = [];
_this.vertexBuffers = [];
_this.vaos = [];
_this.vaoMax = 2;
_this.vertexCount = 0;
_this.renderer.on('prerender', _this.onPrerender, _this);
return _this;
}
/**
* Sets up the renderer context and necessary buffers.
*
* @private
*/
SpriteRenderer.prototype.onContextChange = function onContextChange() {
var gl = this.renderer.gl;
// step 1: first check max textures the GPU can handle.
this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), _settings2.default.SPRITE_MAX_TEXTURES);
// step 2: check the maximum number of if statements the shader can have too..
this.MAX_TEXTURES = (0, _checkMaxIfStatmentsInShader2.default)(this.MAX_TEXTURES, gl);
var shader = this.shader = (0, _generateMultiTextureShader2.default)(gl, this.MAX_TEXTURES);
// create a couple of buffers
this.indexBuffer = _pixiGlCore2.default.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);
// we use the second shader as the first one depending on your browser may omit aTextureId
// as it is not used by the shader so is optimized out.
this.renderer.bindVao(null);
for (var i = 0; i < this.vaoMax; i++) {
this.vertexBuffers[i] = _pixiGlCore2.default.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW);
/* eslint-disable max-len */
// build the vao object that will render..
this.vaos[i] = this.renderer.createVao().addIndex(this.indexBuffer).addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0).addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4).addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4).addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4);
/* eslint-disable max-len */
}
this.vao = this.vaos[0];
this.currentBlendMode = 99999;
this.boundTextures = new Array(this.MAX_TEXTURES);
};
/**
* Called before the renderer starts rendering.
*
*/
SpriteRenderer.prototype.onPrerender = function onPrerender() {
this.vertexCount = 0;
};
/**
* Renders the sprite object.
*
* @param {PIXI.Sprite} sprite - the sprite to render when using this spritebatch
*/
SpriteRenderer.prototype.render = function render(sprite) {
// TODO set blend modes..
// check texture..
if (this.currentIndex >= this.size) {
this.flush();
}
// get the uvs for the texture
// if the uvs have not updated then no point rendering just yet!
if (!sprite._texture._uvs) {
return;
}
// push a texture.
// increment the batchsize
this.sprites[this.currentIndex++] = sprite;
};
/**
* Renders the content and empties the current batch.
*
*/
SpriteRenderer.prototype.flush = function flush() {
if (this.currentIndex === 0) {
return;
}
var gl = this.renderer.gl;
var MAX_TEXTURES = this.MAX_TEXTURES;
var np2 = _bitTwiddle2.default.nextPow2(this.currentIndex);
var log2 = _bitTwiddle2.default.log2(np2);
var buffer = this.buffers[log2];
var sprites = this.sprites;
var groups = this.groups;
var float32View = buffer.float32View;
var uint32View = buffer.uint32View;
var boundTextures = this.boundTextures;
var rendererBoundTextures = this.renderer.boundTextures;
var touch = this.renderer.textureGC.count;
var index = 0;
var nextTexture = void 0;
var currentTexture = void 0;
var groupCount = 1;
var textureCount = 0;
var currentGroup = groups[0];
var vertexData = void 0;
var uvs = void 0;
var blendMode = sprites[0].blendMode;
currentGroup.textureCount = 0;
currentGroup.start = 0;
currentGroup.blend = blendMode;
TICK++;
var i = void 0;
// copy textures..
for (i = 0; i < MAX_TEXTURES; ++i) {
boundTextures[i] = rendererBoundTextures[i];
boundTextures[i]._virtalBoundId = i;
}
for (i = 0; i < this.currentIndex; ++i) {
// upload the sprite elemetns...
// they have all ready been calculated so we just need to push them into the buffer.
var sprite = sprites[i];
nextTexture = sprite._texture.baseTexture;
if (blendMode !== sprite.blendMode) {
// finish a group..
blendMode = sprite.blendMode;
// force the batch to break!
currentTexture = null;
textureCount = MAX_TEXTURES;
TICK++;
}
if (currentTexture !== nextTexture) {
currentTexture = nextTexture;
if (nextTexture._enabled !== TICK) {
if (textureCount === MAX_TEXTURES) {
TICK++;
currentGroup.size = i - currentGroup.start;
textureCount = 0;
currentGroup = groups[groupCount++];
currentGroup.blend = blendMode;
currentGroup.textureCount = 0;
currentGroup.start = i;
}
nextTexture.touched = touch;
if (nextTexture._virtalBoundId === -1) {
for (var j = 0; j < MAX_TEXTURES; ++j) {
var tIndex = (j + TEXTURE_TICK) % MAX_TEXTURES;
var t = boundTextures[tIndex];
if (t._enabled !== TICK) {
TEXTURE_TICK++;
t._virtalBoundId = -1;
nextTexture._virtalBoundId = tIndex;
boundTextures[tIndex] = nextTexture;
break;
}
}
}
nextTexture._enabled = TICK;
currentGroup.textureCount++;
currentGroup.ids[textureCount] = nextTexture._virtalBoundId;
currentGroup.textures[textureCount++] = nextTexture;
}
}
vertexData = sprite.vertexData;
// TODO this sum does not need to be set each frame..
uvs = sprite._texture._uvs.uvsUint32;
if (this.renderer.roundPixels) {
var resolution = this.renderer.resolution;
// xy
float32View[index] = (vertexData[0] * resolution | 0) / resolution;
float32View[index + 1] = (vertexData[1] * resolution | 0) / resolution;
// xy
float32View[index + 5] = (vertexData[2] * resolution | 0) / resolution;
float32View[index + 6] = (vertexData[3] * resolution | 0) / resolution;
// xy
float32View[index + 10] = (vertexData[4] * resolution | 0) / resolution;
float32View[index + 11] = (vertexData[5] * resolution | 0) / resolution;
// xy
float32View[index + 15] = (vertexData[6] * resolution | 0) / resolution;
float32View[index + 16] = (vertexData[7] * resolution | 0) / resolution;
} else {
// xy
float32View[index] = vertexData[0];
float32View[index + 1] = vertexData[1];
// xy
float32View[index + 5] = vertexData[2];
float32View[index + 6] = vertexData[3];
// xy
float32View[index + 10] = vertexData[4];
float32View[index + 11] = vertexData[5];
// xy
float32View[index + 15] = vertexData[6];
float32View[index + 16] = vertexData[7];
}
uint32View[index + 2] = uvs[0];
uint32View[index + 7] = uvs[1];
uint32View[index + 12] = uvs[2];
uint32View[index + 17] = uvs[3];
uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24);
float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = nextTexture._virtalBoundId;
index += 20;
}
currentGroup.size = i - currentGroup.start;
if (!_settings2.default.CAN_UPLOAD_SAME_BUFFER) {
// this is still needed for IOS performance..
// it really does not like uploading to the same buffer in a single frame!
if (this.vaoMax <= this.vertexCount) {
this.vaoMax++;
this.vertexBuffers[this.vertexCount] = _pixiGlCore2.default.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW);
// build the vao object that will render..
this.vaos[this.vertexCount] = this.renderer.createVao().addIndex(this.indexBuffer).addAttribute(this.vertexBuffers[this.vertexCount], this.shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0).addAttribute(this.vertexBuffers[this.vertexCount], this.shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4).addAttribute(this.vertexBuffers[this.vertexCount], this.shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4).addAttribute(this.vertexBuffers[this.vertexCount], this.shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4);
}
this.renderer.bindVao(this.vaos[this.vertexCount]);
this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false);
this.vertexCount++;
} else {
// lets use the faster option, always use buffer number 0
this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, true);
}
for (i = 0; i < MAX_TEXTURES; ++i) {
rendererBoundTextures[i]._virtalBoundId = -1;
}
// render the groups..
for (i = 0; i < groupCount; ++i) {
var group = groups[i];
var groupTextureCount = group.textureCount;
for (var _j = 0; _j < groupTextureCount; _j++) {
currentTexture = group.textures[_j];
// reset virtual ids..
// lets do a quick check..
if (rendererBoundTextures[group.ids[_j]] !== currentTexture) {
this.renderer.bindTexture(currentTexture, group.ids[_j], true);
}
// reset the virtualId..
currentTexture._virtalBoundId = -1;
}
// set the blend mode..
this.renderer.state.setBlendMode(group.blend);
gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2);
}
// reset elements for the next flush
this.currentIndex = 0;
};
/**
* Starts a new sprite batch.
*/
SpriteRenderer.prototype.start = function start() {
this.renderer.bindShader(this.shader);
if (_settings2.default.CAN_UPLOAD_SAME_BUFFER) {
// bind buffer #0, we don't need others
this.renderer.bindVao(this.vaos[this.vertexCount]);
this.vertexBuffers[this.vertexCount].bind();
}
};
/**
* Stops and flushes the current batch.
*
*/
SpriteRenderer.prototype.stop = function stop() {
this.flush();
};
/**
* Destroys the SpriteRenderer.
*
*/
SpriteRenderer.prototype.destroy = function destroy() {
for (var i = 0; i < this.vaoMax; i++) {
if (this.vertexBuffers[i]) {
this.vertexBuffers[i].destroy();
}
if (this.vaos[i]) {
this.vaos[i].destroy();
}
}
if (this.indexBuffer) {
this.indexBuffer.destroy();
}
this.renderer.off('prerender', this.onPrerender, this);
_ObjectRenderer.prototype.destroy.call(this);
if (this.shader) {
this.shader.destroy();
this.shader = null;
}
this.vertexBuffers = null;
this.vaos = null;
this.indexBuffer = null;
this.indices = null;
this.sprites = null;
for (var _i = 0; _i < this.buffers.length; ++_i) {
this.buffers[_i].destroy();
}
};
return SpriteRenderer;
}(_ObjectRenderer3.default);
exports.default = SpriteRenderer;
_WebGLRenderer2.default.registerPlugin('sprite', SpriteRenderer);
//# sourceMappingURL=SpriteRenderer.js.map