diff --git a/packages/core/src/batch/BatchPluginFactory.js b/packages/core/src/batch/BatchPluginFactory.js new file mode 100644 index 0000000..22bd0ba --- /dev/null +++ b/packages/core/src/batch/BatchPluginFactory.js @@ -0,0 +1,94 @@ +import BatchShaderGenerator from './BatchShaderGenerator'; +import BatchGeometry from './BatchGeometry'; +import BaseBatchRenderer from './BatchRenderer'; + +import defaultVertex from './texture.vert'; +import defaultFragment from './texture.frag'; + +/** + * Used to create new, custom BatchRenderer plugins for the Renderer. + * @example + * const fragment = ` + * varying vec2 vTextureCoord; + * varying vec4 vColor; + * varying float vTextureId; + * uniform sampler2D uSamplers[%count%]; + * + * void main(void){ + * vec4 color; + * %forloop% + * gl_FragColor = vColor * vec4(color.a - color.rgb, color.a); + * } + * `; + * const InvertBatchRenderer = PIXI.BatchPluginFactory.create({ fragment }); + * PIXI.Renderer.registerPlugin('invert', InvertBatchRenderer); + * const sprite = new PIXI.Sprite(); + * sprite.pluginName = 'invert'; + * + * @class + * @memberof PIXI + */ +export class BatchPluginFactory +{ + /** + * Create a new BatchRenderer plugin for Renderer. this convenience can provide an easy way + * to extend BatchRenderer with all the necessary pieces. + * + * @static + * @param {object} [options] + * @param {string} [options.vertex=PIXI.BatchPluginFactory.defaultVertexSrc] - Vertex shader source + * @param {string} [options.fragment=PIXI.BatchPluginFactory.defaultFragmentTemplate] - Fragment shader template + * @param {number} [options.vertexSize=6] - Vertex size + * @param {object} [options.geometryClass=PIXI.BatchGeometry] + * @return {PIXI.BatchRenderer} New batch renderer plugin. + */ + static create(options) + { + const { vertex, fragment, vertexSize, geometryClass } = Object.assign({ + vertex: defaultVertex, + fragment: defaultFragment, + geometryClass: BatchGeometry, + vertexSize: 6, + }, options); + + return class BatchPlugin extends BaseBatchRenderer + { + constructor(renderer) + { + super(renderer); + + this.shaderGenerator = new BatchShaderGenerator(vertex, fragment); + this.geometryClass = geometryClass; + this.vertexSize = vertexSize; + } + }; + } + + /** + * The default vertex shader source + * + * @static + * @type {string} + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @type {string} + * @constant + */ + static get defaultFragmentTemplate() + { + return defaultFragment; + } +} + +// Setup the default BatchRenderer plugin, this is what +// we'll actually export at the root level +export const BatchRenderer = BatchPluginFactory.create(); diff --git a/packages/core/src/batch/BatchPluginFactory.js b/packages/core/src/batch/BatchPluginFactory.js new file mode 100644 index 0000000..22bd0ba --- /dev/null +++ b/packages/core/src/batch/BatchPluginFactory.js @@ -0,0 +1,94 @@ +import BatchShaderGenerator from './BatchShaderGenerator'; +import BatchGeometry from './BatchGeometry'; +import BaseBatchRenderer from './BatchRenderer'; + +import defaultVertex from './texture.vert'; +import defaultFragment from './texture.frag'; + +/** + * Used to create new, custom BatchRenderer plugins for the Renderer. + * @example + * const fragment = ` + * varying vec2 vTextureCoord; + * varying vec4 vColor; + * varying float vTextureId; + * uniform sampler2D uSamplers[%count%]; + * + * void main(void){ + * vec4 color; + * %forloop% + * gl_FragColor = vColor * vec4(color.a - color.rgb, color.a); + * } + * `; + * const InvertBatchRenderer = PIXI.BatchPluginFactory.create({ fragment }); + * PIXI.Renderer.registerPlugin('invert', InvertBatchRenderer); + * const sprite = new PIXI.Sprite(); + * sprite.pluginName = 'invert'; + * + * @class + * @memberof PIXI + */ +export class BatchPluginFactory +{ + /** + * Create a new BatchRenderer plugin for Renderer. this convenience can provide an easy way + * to extend BatchRenderer with all the necessary pieces. + * + * @static + * @param {object} [options] + * @param {string} [options.vertex=PIXI.BatchPluginFactory.defaultVertexSrc] - Vertex shader source + * @param {string} [options.fragment=PIXI.BatchPluginFactory.defaultFragmentTemplate] - Fragment shader template + * @param {number} [options.vertexSize=6] - Vertex size + * @param {object} [options.geometryClass=PIXI.BatchGeometry] + * @return {PIXI.BatchRenderer} New batch renderer plugin. + */ + static create(options) + { + const { vertex, fragment, vertexSize, geometryClass } = Object.assign({ + vertex: defaultVertex, + fragment: defaultFragment, + geometryClass: BatchGeometry, + vertexSize: 6, + }, options); + + return class BatchPlugin extends BaseBatchRenderer + { + constructor(renderer) + { + super(renderer); + + this.shaderGenerator = new BatchShaderGenerator(vertex, fragment); + this.geometryClass = geometryClass; + this.vertexSize = vertexSize; + } + }; + } + + /** + * The default vertex shader source + * + * @static + * @type {string} + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @type {string} + * @constant + */ + static get defaultFragmentTemplate() + { + return defaultFragment; + } +} + +// Setup the default BatchRenderer plugin, this is what +// we'll actually export at the root level +export const BatchRenderer = BatchPluginFactory.create(); diff --git a/packages/core/src/batch/BatchRenderer.js b/packages/core/src/batch/BatchRenderer.js index bddddca..690650f 100644 --- a/packages/core/src/batch/BatchRenderer.js +++ b/packages/core/src/batch/BatchRenderer.js @@ -1,16 +1,11 @@ -import BatchGeometry from './BatchGeometry'; import BatchDrawCall from './BatchDrawCall'; import BaseTexture from '../textures/BaseTexture'; - import State from '../state/State'; import ObjectRenderer from './ObjectRenderer'; import checkMaxIfStatementsInShader from '../shader/utils/checkMaxIfStatementsInShader'; - import { settings } from '@pixi/settings'; import { premultiplyBlendMode, premultiplyTint, nextPow2, log2 } from '@pixi/utils'; - import BatchBuffer from './BatchBuffer'; -import generateMultiTextureShader from './generateMultiTextureShader'; import { ENV } from '@pixi/constants'; /** @@ -31,21 +26,6 @@ super(renderer); /** - * Number of values sent in the vertex buffer. - * aVertexPosition(2), aTextureCoord(1), aColor(1), aTextureId(1) = 5 - * - * @member {number} - */ - this.vertSize = 6; - - /** - * 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} @@ -79,7 +59,7 @@ * 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 rendered. * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} + * @member {PIXI.Shader} */ this.shader = null; @@ -100,6 +80,29 @@ this.renderer.on('prerender', this.onPrerender, this); this.state = State.for2d(); + + /** + * MultiTexture shader generator. + * + * @member {PIXI.BatchShaderGenerator} + */ + this.shaderGenerator = null; + + /** + * The class we use to create geometries. + * Please override it in constructor + * @member {object} + * @default PIXI.BatchGeometry + */ + this.geometryClass = null; + + /** + * Number of values sent in the vertex buffer. + * aVertexPosition(2), aTextureCoord(1), aColor(1), aTextureId(1) = 5 + * + * @member {number} vertSize + */ + this.vertexSize = null; } /** @@ -122,15 +125,14 @@ this.MAX_TEXTURES = checkMaxIfStatementsInShader(this.MAX_TEXTURES, gl); } - // generate generateMultiTextureProgram, may be a better move? - this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); + this.shader = this.shaderGenerator.generateShader(this.MAX_TEXTURES); // 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. for (let i = 0; i < this.vaoMax; i++) { /* eslint-disable max-len */ - this.vaos[i] = new BatchGeometry(); + this.vaos[i] = new (this.geometryClass)(); } } @@ -204,7 +206,7 @@ if (!buffer) { - this.aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertByteSize); + this.aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertexSize * 4); } return buffer; @@ -223,6 +225,7 @@ const gl = this.renderer.gl; const MAX_TEXTURES = this.MAX_TEXTURES; + const vertSize = this.vertexSize; const buffer = this.getAttributeBuffer(this.currentSize); const indexBuffer = this.getIndexBuffer(this.currentIndexSize); @@ -309,7 +312,7 @@ this.packGeometry(sprite, float32View, uint32View, indexBuffer, index, indexCount);// argb, nextTexture._id, float32View, uint32View, indexBuffer, index, indexCount); // push a graphics.. - index += (sprite.vertexData.length / 2) * this.vertSize; + index += (sprite.vertexData.length / 2) * vertSize; indexCount += sprite.indices.length; } @@ -327,7 +330,7 @@ { this.vaoMax++; /* eslint-disable max-len */ - this.vaos[this.vertexCount] = new BatchGeometry(); + this.vaos[this.vertexCount] = new (this.geometryClass)(); } this.vaos[this.vertexCount]._buffer.update(buffer.vertices, 0); @@ -391,7 +394,7 @@ packGeometry(element, float32View, uint32View, indexBuffer, index, indexCount) { - const p = index / this.vertSize;// float32View.length / 6 / 2; + const p = index / this.vertexSize;// float32View.length / 6 / 2; const uvs = element.uvs; const indicies = element.indices;// geometry.getIndex().data;// indicies; const vertexData = element.vertexData; diff --git a/packages/core/src/batch/BatchPluginFactory.js b/packages/core/src/batch/BatchPluginFactory.js new file mode 100644 index 0000000..22bd0ba --- /dev/null +++ b/packages/core/src/batch/BatchPluginFactory.js @@ -0,0 +1,94 @@ +import BatchShaderGenerator from './BatchShaderGenerator'; +import BatchGeometry from './BatchGeometry'; +import BaseBatchRenderer from './BatchRenderer'; + +import defaultVertex from './texture.vert'; +import defaultFragment from './texture.frag'; + +/** + * Used to create new, custom BatchRenderer plugins for the Renderer. + * @example + * const fragment = ` + * varying vec2 vTextureCoord; + * varying vec4 vColor; + * varying float vTextureId; + * uniform sampler2D uSamplers[%count%]; + * + * void main(void){ + * vec4 color; + * %forloop% + * gl_FragColor = vColor * vec4(color.a - color.rgb, color.a); + * } + * `; + * const InvertBatchRenderer = PIXI.BatchPluginFactory.create({ fragment }); + * PIXI.Renderer.registerPlugin('invert', InvertBatchRenderer); + * const sprite = new PIXI.Sprite(); + * sprite.pluginName = 'invert'; + * + * @class + * @memberof PIXI + */ +export class BatchPluginFactory +{ + /** + * Create a new BatchRenderer plugin for Renderer. this convenience can provide an easy way + * to extend BatchRenderer with all the necessary pieces. + * + * @static + * @param {object} [options] + * @param {string} [options.vertex=PIXI.BatchPluginFactory.defaultVertexSrc] - Vertex shader source + * @param {string} [options.fragment=PIXI.BatchPluginFactory.defaultFragmentTemplate] - Fragment shader template + * @param {number} [options.vertexSize=6] - Vertex size + * @param {object} [options.geometryClass=PIXI.BatchGeometry] + * @return {PIXI.BatchRenderer} New batch renderer plugin. + */ + static create(options) + { + const { vertex, fragment, vertexSize, geometryClass } = Object.assign({ + vertex: defaultVertex, + fragment: defaultFragment, + geometryClass: BatchGeometry, + vertexSize: 6, + }, options); + + return class BatchPlugin extends BaseBatchRenderer + { + constructor(renderer) + { + super(renderer); + + this.shaderGenerator = new BatchShaderGenerator(vertex, fragment); + this.geometryClass = geometryClass; + this.vertexSize = vertexSize; + } + }; + } + + /** + * The default vertex shader source + * + * @static + * @type {string} + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @type {string} + * @constant + */ + static get defaultFragmentTemplate() + { + return defaultFragment; + } +} + +// Setup the default BatchRenderer plugin, this is what +// we'll actually export at the root level +export const BatchRenderer = BatchPluginFactory.create(); diff --git a/packages/core/src/batch/BatchRenderer.js b/packages/core/src/batch/BatchRenderer.js index bddddca..690650f 100644 --- a/packages/core/src/batch/BatchRenderer.js +++ b/packages/core/src/batch/BatchRenderer.js @@ -1,16 +1,11 @@ -import BatchGeometry from './BatchGeometry'; import BatchDrawCall from './BatchDrawCall'; import BaseTexture from '../textures/BaseTexture'; - import State from '../state/State'; import ObjectRenderer from './ObjectRenderer'; import checkMaxIfStatementsInShader from '../shader/utils/checkMaxIfStatementsInShader'; - import { settings } from '@pixi/settings'; import { premultiplyBlendMode, premultiplyTint, nextPow2, log2 } from '@pixi/utils'; - import BatchBuffer from './BatchBuffer'; -import generateMultiTextureShader from './generateMultiTextureShader'; import { ENV } from '@pixi/constants'; /** @@ -31,21 +26,6 @@ super(renderer); /** - * Number of values sent in the vertex buffer. - * aVertexPosition(2), aTextureCoord(1), aColor(1), aTextureId(1) = 5 - * - * @member {number} - */ - this.vertSize = 6; - - /** - * 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} @@ -79,7 +59,7 @@ * 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 rendered. * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} + * @member {PIXI.Shader} */ this.shader = null; @@ -100,6 +80,29 @@ this.renderer.on('prerender', this.onPrerender, this); this.state = State.for2d(); + + /** + * MultiTexture shader generator. + * + * @member {PIXI.BatchShaderGenerator} + */ + this.shaderGenerator = null; + + /** + * The class we use to create geometries. + * Please override it in constructor + * @member {object} + * @default PIXI.BatchGeometry + */ + this.geometryClass = null; + + /** + * Number of values sent in the vertex buffer. + * aVertexPosition(2), aTextureCoord(1), aColor(1), aTextureId(1) = 5 + * + * @member {number} vertSize + */ + this.vertexSize = null; } /** @@ -122,15 +125,14 @@ this.MAX_TEXTURES = checkMaxIfStatementsInShader(this.MAX_TEXTURES, gl); } - // generate generateMultiTextureProgram, may be a better move? - this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); + this.shader = this.shaderGenerator.generateShader(this.MAX_TEXTURES); // 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. for (let i = 0; i < this.vaoMax; i++) { /* eslint-disable max-len */ - this.vaos[i] = new BatchGeometry(); + this.vaos[i] = new (this.geometryClass)(); } } @@ -204,7 +206,7 @@ if (!buffer) { - this.aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertByteSize); + this.aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertexSize * 4); } return buffer; @@ -223,6 +225,7 @@ const gl = this.renderer.gl; const MAX_TEXTURES = this.MAX_TEXTURES; + const vertSize = this.vertexSize; const buffer = this.getAttributeBuffer(this.currentSize); const indexBuffer = this.getIndexBuffer(this.currentIndexSize); @@ -309,7 +312,7 @@ this.packGeometry(sprite, float32View, uint32View, indexBuffer, index, indexCount);// argb, nextTexture._id, float32View, uint32View, indexBuffer, index, indexCount); // push a graphics.. - index += (sprite.vertexData.length / 2) * this.vertSize; + index += (sprite.vertexData.length / 2) * vertSize; indexCount += sprite.indices.length; } @@ -327,7 +330,7 @@ { this.vaoMax++; /* eslint-disable max-len */ - this.vaos[this.vertexCount] = new BatchGeometry(); + this.vaos[this.vertexCount] = new (this.geometryClass)(); } this.vaos[this.vertexCount]._buffer.update(buffer.vertices, 0); @@ -391,7 +394,7 @@ packGeometry(element, float32View, uint32View, indexBuffer, index, indexCount) { - const p = index / this.vertSize;// float32View.length / 6 / 2; + const p = index / this.vertexSize;// float32View.length / 6 / 2; const uvs = element.uvs; const indicies = element.indices;// geometry.getIndex().data;// indicies; const vertexData = element.vertexData; diff --git a/packages/core/src/batch/BatchShaderGenerator.js b/packages/core/src/batch/BatchShaderGenerator.js new file mode 100644 index 0000000..4abd176 --- /dev/null +++ b/packages/core/src/batch/BatchShaderGenerator.js @@ -0,0 +1,107 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import UniformGroup from '../shader/UniformGroup'; +import { Matrix } from '@pixi/math'; + +/** + * Helper that generates batching multi-texture shader. Use it with your new BatchRenderer + * + * @class + * @memberof PIXI + */ +export default class BatchShaderGenerator +{ + /** + * @param {string} vertexSrc - Vertex shader + * @param {string} fragTemplate - Fragment shader template + */ + constructor(vertexSrc, fragTemplate) + { + /** + * Reference to the vertex shader source. + * + * @member {string} + */ + this.vertexSrc = vertexSrc; + + /** + * Reference to the fragement shader template. Must contain "%count%" and "%forloop%". + * + * @member {string} + */ + this.fragTemplate = fragTemplate; + + this.programCache = {}; + this.defaultGroupCache = {}; + + if (fragTemplate.indexOf('%count%') < 0) + { + throw new Error('Fragment template must contain "%count%".'); + } + + if (fragTemplate.indexOf('%forloop%') < 0) + { + throw new Error('Fragment template must contain "%forloop%".'); + } + } + + generateShader(maxTextures) + { + if (!this.programCache[maxTextures]) + { + const sampleValues = new Int32Array(maxTextures); + + for (let i = 0; i < maxTextures; i++) + { + sampleValues[i] = i; + } + + this.defaultGroupCache[maxTextures] = UniformGroup.from({ uSamplers: sampleValues }, true); + + let fragmentSrc = this.fragTemplate; + + fragmentSrc = fragmentSrc.replace(/%count%/gi, `${maxTextures}`); + fragmentSrc = fragmentSrc.replace(/%forloop%/gi, this.generateSampleSrc(maxTextures)); + + this.programCache[maxTextures] = new Program(this.vertexSrc, fragmentSrc); + } + + const uniforms = { + tint: new Float32Array([1, 1, 1, 1]), + translationMatrix: new Matrix(), + default: this.defaultGroupCache[maxTextures], + }; + + return new Shader(this.programCache[maxTextures], uniforms); + } + + generateSampleSrc(maxTextures) + { + let src = ''; + + src += '\n'; + src += '\n'; + + for (let i = 0; i < maxTextures; i++) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxTextures - 1) + { + src += `if(vTextureId < ${i}.5)`; + } + + src += '\n{'; + src += `\n\tcolor = texture2D(uSamplers[${i}], vTextureCoord);`; + src += '\n}'; + } + + src += '\n'; + src += '\n'; + + return src; + } +} diff --git a/packages/core/src/batch/BatchPluginFactory.js b/packages/core/src/batch/BatchPluginFactory.js new file mode 100644 index 0000000..22bd0ba --- /dev/null +++ b/packages/core/src/batch/BatchPluginFactory.js @@ -0,0 +1,94 @@ +import BatchShaderGenerator from './BatchShaderGenerator'; +import BatchGeometry from './BatchGeometry'; +import BaseBatchRenderer from './BatchRenderer'; + +import defaultVertex from './texture.vert'; +import defaultFragment from './texture.frag'; + +/** + * Used to create new, custom BatchRenderer plugins for the Renderer. + * @example + * const fragment = ` + * varying vec2 vTextureCoord; + * varying vec4 vColor; + * varying float vTextureId; + * uniform sampler2D uSamplers[%count%]; + * + * void main(void){ + * vec4 color; + * %forloop% + * gl_FragColor = vColor * vec4(color.a - color.rgb, color.a); + * } + * `; + * const InvertBatchRenderer = PIXI.BatchPluginFactory.create({ fragment }); + * PIXI.Renderer.registerPlugin('invert', InvertBatchRenderer); + * const sprite = new PIXI.Sprite(); + * sprite.pluginName = 'invert'; + * + * @class + * @memberof PIXI + */ +export class BatchPluginFactory +{ + /** + * Create a new BatchRenderer plugin for Renderer. this convenience can provide an easy way + * to extend BatchRenderer with all the necessary pieces. + * + * @static + * @param {object} [options] + * @param {string} [options.vertex=PIXI.BatchPluginFactory.defaultVertexSrc] - Vertex shader source + * @param {string} [options.fragment=PIXI.BatchPluginFactory.defaultFragmentTemplate] - Fragment shader template + * @param {number} [options.vertexSize=6] - Vertex size + * @param {object} [options.geometryClass=PIXI.BatchGeometry] + * @return {PIXI.BatchRenderer} New batch renderer plugin. + */ + static create(options) + { + const { vertex, fragment, vertexSize, geometryClass } = Object.assign({ + vertex: defaultVertex, + fragment: defaultFragment, + geometryClass: BatchGeometry, + vertexSize: 6, + }, options); + + return class BatchPlugin extends BaseBatchRenderer + { + constructor(renderer) + { + super(renderer); + + this.shaderGenerator = new BatchShaderGenerator(vertex, fragment); + this.geometryClass = geometryClass; + this.vertexSize = vertexSize; + } + }; + } + + /** + * The default vertex shader source + * + * @static + * @type {string} + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @type {string} + * @constant + */ + static get defaultFragmentTemplate() + { + return defaultFragment; + } +} + +// Setup the default BatchRenderer plugin, this is what +// we'll actually export at the root level +export const BatchRenderer = BatchPluginFactory.create(); diff --git a/packages/core/src/batch/BatchRenderer.js b/packages/core/src/batch/BatchRenderer.js index bddddca..690650f 100644 --- a/packages/core/src/batch/BatchRenderer.js +++ b/packages/core/src/batch/BatchRenderer.js @@ -1,16 +1,11 @@ -import BatchGeometry from './BatchGeometry'; import BatchDrawCall from './BatchDrawCall'; import BaseTexture from '../textures/BaseTexture'; - import State from '../state/State'; import ObjectRenderer from './ObjectRenderer'; import checkMaxIfStatementsInShader from '../shader/utils/checkMaxIfStatementsInShader'; - import { settings } from '@pixi/settings'; import { premultiplyBlendMode, premultiplyTint, nextPow2, log2 } from '@pixi/utils'; - import BatchBuffer from './BatchBuffer'; -import generateMultiTextureShader from './generateMultiTextureShader'; import { ENV } from '@pixi/constants'; /** @@ -31,21 +26,6 @@ super(renderer); /** - * Number of values sent in the vertex buffer. - * aVertexPosition(2), aTextureCoord(1), aColor(1), aTextureId(1) = 5 - * - * @member {number} - */ - this.vertSize = 6; - - /** - * 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} @@ -79,7 +59,7 @@ * 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 rendered. * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} + * @member {PIXI.Shader} */ this.shader = null; @@ -100,6 +80,29 @@ this.renderer.on('prerender', this.onPrerender, this); this.state = State.for2d(); + + /** + * MultiTexture shader generator. + * + * @member {PIXI.BatchShaderGenerator} + */ + this.shaderGenerator = null; + + /** + * The class we use to create geometries. + * Please override it in constructor + * @member {object} + * @default PIXI.BatchGeometry + */ + this.geometryClass = null; + + /** + * Number of values sent in the vertex buffer. + * aVertexPosition(2), aTextureCoord(1), aColor(1), aTextureId(1) = 5 + * + * @member {number} vertSize + */ + this.vertexSize = null; } /** @@ -122,15 +125,14 @@ this.MAX_TEXTURES = checkMaxIfStatementsInShader(this.MAX_TEXTURES, gl); } - // generate generateMultiTextureProgram, may be a better move? - this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); + this.shader = this.shaderGenerator.generateShader(this.MAX_TEXTURES); // 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. for (let i = 0; i < this.vaoMax; i++) { /* eslint-disable max-len */ - this.vaos[i] = new BatchGeometry(); + this.vaos[i] = new (this.geometryClass)(); } } @@ -204,7 +206,7 @@ if (!buffer) { - this.aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertByteSize); + this.aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertexSize * 4); } return buffer; @@ -223,6 +225,7 @@ const gl = this.renderer.gl; const MAX_TEXTURES = this.MAX_TEXTURES; + const vertSize = this.vertexSize; const buffer = this.getAttributeBuffer(this.currentSize); const indexBuffer = this.getIndexBuffer(this.currentIndexSize); @@ -309,7 +312,7 @@ this.packGeometry(sprite, float32View, uint32View, indexBuffer, index, indexCount);// argb, nextTexture._id, float32View, uint32View, indexBuffer, index, indexCount); // push a graphics.. - index += (sprite.vertexData.length / 2) * this.vertSize; + index += (sprite.vertexData.length / 2) * vertSize; indexCount += sprite.indices.length; } @@ -327,7 +330,7 @@ { this.vaoMax++; /* eslint-disable max-len */ - this.vaos[this.vertexCount] = new BatchGeometry(); + this.vaos[this.vertexCount] = new (this.geometryClass)(); } this.vaos[this.vertexCount]._buffer.update(buffer.vertices, 0); @@ -391,7 +394,7 @@ packGeometry(element, float32View, uint32View, indexBuffer, index, indexCount) { - const p = index / this.vertSize;// float32View.length / 6 / 2; + const p = index / this.vertexSize;// float32View.length / 6 / 2; const uvs = element.uvs; const indicies = element.indices;// geometry.getIndex().data;// indicies; const vertexData = element.vertexData; diff --git a/packages/core/src/batch/BatchShaderGenerator.js b/packages/core/src/batch/BatchShaderGenerator.js new file mode 100644 index 0000000..4abd176 --- /dev/null +++ b/packages/core/src/batch/BatchShaderGenerator.js @@ -0,0 +1,107 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import UniformGroup from '../shader/UniformGroup'; +import { Matrix } from '@pixi/math'; + +/** + * Helper that generates batching multi-texture shader. Use it with your new BatchRenderer + * + * @class + * @memberof PIXI + */ +export default class BatchShaderGenerator +{ + /** + * @param {string} vertexSrc - Vertex shader + * @param {string} fragTemplate - Fragment shader template + */ + constructor(vertexSrc, fragTemplate) + { + /** + * Reference to the vertex shader source. + * + * @member {string} + */ + this.vertexSrc = vertexSrc; + + /** + * Reference to the fragement shader template. Must contain "%count%" and "%forloop%". + * + * @member {string} + */ + this.fragTemplate = fragTemplate; + + this.programCache = {}; + this.defaultGroupCache = {}; + + if (fragTemplate.indexOf('%count%') < 0) + { + throw new Error('Fragment template must contain "%count%".'); + } + + if (fragTemplate.indexOf('%forloop%') < 0) + { + throw new Error('Fragment template must contain "%forloop%".'); + } + } + + generateShader(maxTextures) + { + if (!this.programCache[maxTextures]) + { + const sampleValues = new Int32Array(maxTextures); + + for (let i = 0; i < maxTextures; i++) + { + sampleValues[i] = i; + } + + this.defaultGroupCache[maxTextures] = UniformGroup.from({ uSamplers: sampleValues }, true); + + let fragmentSrc = this.fragTemplate; + + fragmentSrc = fragmentSrc.replace(/%count%/gi, `${maxTextures}`); + fragmentSrc = fragmentSrc.replace(/%forloop%/gi, this.generateSampleSrc(maxTextures)); + + this.programCache[maxTextures] = new Program(this.vertexSrc, fragmentSrc); + } + + const uniforms = { + tint: new Float32Array([1, 1, 1, 1]), + translationMatrix: new Matrix(), + default: this.defaultGroupCache[maxTextures], + }; + + return new Shader(this.programCache[maxTextures], uniforms); + } + + generateSampleSrc(maxTextures) + { + let src = ''; + + src += '\n'; + src += '\n'; + + for (let i = 0; i < maxTextures; i++) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxTextures - 1) + { + src += `if(vTextureId < ${i}.5)`; + } + + src += '\n{'; + src += `\n\tcolor = texture2D(uSamplers[${i}], vTextureCoord);`; + src += '\n}'; + } + + src += '\n'; + src += '\n'; + + return src; + } +} diff --git a/packages/core/src/batch/generateMultiTextureShader.js b/packages/core/src/batch/generateMultiTextureShader.js deleted file mode 100644 index 20d534d..0000000 --- a/packages/core/src/batch/generateMultiTextureShader.js +++ /dev/null @@ -1,84 +0,0 @@ -import Shader from '../shader/Shader'; -import Program from '../shader/Program'; -import UniformGroup from '../shader/UniformGroup'; - -import vertex from './texture.vert'; -import { Matrix } from '@pixi/math'; - -const fragTemplate = [ - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - 'varying float vTextureId;', - 'uniform sampler2D uSamplers[%count%];', - - 'void main(void){', - 'vec4 color;', - '%forloop%', - 'gl_FragColor = color * vColor;', - '}', -].join('\n'); - -const defaultGroupCache = {}; -const programCache = {}; - -export default function generateMultiTextureShader(gl, maxTextures) -{ - if (!programCache[maxTextures]) - { - const sampleValues = new Int32Array(maxTextures); - - for (let i = 0; i < maxTextures; i++) - { - sampleValues[i] = i; - } - - defaultGroupCache[maxTextures] = UniformGroup.from({ uSamplers: sampleValues }, true); - - let fragmentSrc = fragTemplate; - - fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); - fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); - - programCache[maxTextures] = new Program(vertex, fragmentSrc); - } - - const uniforms = { - tint: new Float32Array([1, 1, 1, 1]), - translationMatrix: new Matrix(), - default: defaultGroupCache[maxTextures], - }; - - const shader = new Shader(programCache[maxTextures], uniforms); - - return shader; -} - -function generateSampleSrc(maxTextures) -{ - let src = ''; - - src += '\n'; - src += '\n'; - - for (let i = 0; i < maxTextures; i++) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxTextures - 1) - { - src += `if(vTextureId < ${i}.5)`; - } - - src += '\n{'; - src += `\n\tcolor = texture2D(uSamplers[${i}], vTextureCoord);`; - src += '\n}'; - } - - src += '\n'; - src += '\n'; - - return src; -} diff --git a/packages/core/src/batch/BatchPluginFactory.js b/packages/core/src/batch/BatchPluginFactory.js new file mode 100644 index 0000000..22bd0ba --- /dev/null +++ b/packages/core/src/batch/BatchPluginFactory.js @@ -0,0 +1,94 @@ +import BatchShaderGenerator from './BatchShaderGenerator'; +import BatchGeometry from './BatchGeometry'; +import BaseBatchRenderer from './BatchRenderer'; + +import defaultVertex from './texture.vert'; +import defaultFragment from './texture.frag'; + +/** + * Used to create new, custom BatchRenderer plugins for the Renderer. + * @example + * const fragment = ` + * varying vec2 vTextureCoord; + * varying vec4 vColor; + * varying float vTextureId; + * uniform sampler2D uSamplers[%count%]; + * + * void main(void){ + * vec4 color; + * %forloop% + * gl_FragColor = vColor * vec4(color.a - color.rgb, color.a); + * } + * `; + * const InvertBatchRenderer = PIXI.BatchPluginFactory.create({ fragment }); + * PIXI.Renderer.registerPlugin('invert', InvertBatchRenderer); + * const sprite = new PIXI.Sprite(); + * sprite.pluginName = 'invert'; + * + * @class + * @memberof PIXI + */ +export class BatchPluginFactory +{ + /** + * Create a new BatchRenderer plugin for Renderer. this convenience can provide an easy way + * to extend BatchRenderer with all the necessary pieces. + * + * @static + * @param {object} [options] + * @param {string} [options.vertex=PIXI.BatchPluginFactory.defaultVertexSrc] - Vertex shader source + * @param {string} [options.fragment=PIXI.BatchPluginFactory.defaultFragmentTemplate] - Fragment shader template + * @param {number} [options.vertexSize=6] - Vertex size + * @param {object} [options.geometryClass=PIXI.BatchGeometry] + * @return {PIXI.BatchRenderer} New batch renderer plugin. + */ + static create(options) + { + const { vertex, fragment, vertexSize, geometryClass } = Object.assign({ + vertex: defaultVertex, + fragment: defaultFragment, + geometryClass: BatchGeometry, + vertexSize: 6, + }, options); + + return class BatchPlugin extends BaseBatchRenderer + { + constructor(renderer) + { + super(renderer); + + this.shaderGenerator = new BatchShaderGenerator(vertex, fragment); + this.geometryClass = geometryClass; + this.vertexSize = vertexSize; + } + }; + } + + /** + * The default vertex shader source + * + * @static + * @type {string} + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @type {string} + * @constant + */ + static get defaultFragmentTemplate() + { + return defaultFragment; + } +} + +// Setup the default BatchRenderer plugin, this is what +// we'll actually export at the root level +export const BatchRenderer = BatchPluginFactory.create(); diff --git a/packages/core/src/batch/BatchRenderer.js b/packages/core/src/batch/BatchRenderer.js index bddddca..690650f 100644 --- a/packages/core/src/batch/BatchRenderer.js +++ b/packages/core/src/batch/BatchRenderer.js @@ -1,16 +1,11 @@ -import BatchGeometry from './BatchGeometry'; import BatchDrawCall from './BatchDrawCall'; import BaseTexture from '../textures/BaseTexture'; - import State from '../state/State'; import ObjectRenderer from './ObjectRenderer'; import checkMaxIfStatementsInShader from '../shader/utils/checkMaxIfStatementsInShader'; - import { settings } from '@pixi/settings'; import { premultiplyBlendMode, premultiplyTint, nextPow2, log2 } from '@pixi/utils'; - import BatchBuffer from './BatchBuffer'; -import generateMultiTextureShader from './generateMultiTextureShader'; import { ENV } from '@pixi/constants'; /** @@ -31,21 +26,6 @@ super(renderer); /** - * Number of values sent in the vertex buffer. - * aVertexPosition(2), aTextureCoord(1), aColor(1), aTextureId(1) = 5 - * - * @member {number} - */ - this.vertSize = 6; - - /** - * 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} @@ -79,7 +59,7 @@ * 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 rendered. * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} + * @member {PIXI.Shader} */ this.shader = null; @@ -100,6 +80,29 @@ this.renderer.on('prerender', this.onPrerender, this); this.state = State.for2d(); + + /** + * MultiTexture shader generator. + * + * @member {PIXI.BatchShaderGenerator} + */ + this.shaderGenerator = null; + + /** + * The class we use to create geometries. + * Please override it in constructor + * @member {object} + * @default PIXI.BatchGeometry + */ + this.geometryClass = null; + + /** + * Number of values sent in the vertex buffer. + * aVertexPosition(2), aTextureCoord(1), aColor(1), aTextureId(1) = 5 + * + * @member {number} vertSize + */ + this.vertexSize = null; } /** @@ -122,15 +125,14 @@ this.MAX_TEXTURES = checkMaxIfStatementsInShader(this.MAX_TEXTURES, gl); } - // generate generateMultiTextureProgram, may be a better move? - this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); + this.shader = this.shaderGenerator.generateShader(this.MAX_TEXTURES); // 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. for (let i = 0; i < this.vaoMax; i++) { /* eslint-disable max-len */ - this.vaos[i] = new BatchGeometry(); + this.vaos[i] = new (this.geometryClass)(); } } @@ -204,7 +206,7 @@ if (!buffer) { - this.aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertByteSize); + this.aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertexSize * 4); } return buffer; @@ -223,6 +225,7 @@ const gl = this.renderer.gl; const MAX_TEXTURES = this.MAX_TEXTURES; + const vertSize = this.vertexSize; const buffer = this.getAttributeBuffer(this.currentSize); const indexBuffer = this.getIndexBuffer(this.currentIndexSize); @@ -309,7 +312,7 @@ this.packGeometry(sprite, float32View, uint32View, indexBuffer, index, indexCount);// argb, nextTexture._id, float32View, uint32View, indexBuffer, index, indexCount); // push a graphics.. - index += (sprite.vertexData.length / 2) * this.vertSize; + index += (sprite.vertexData.length / 2) * vertSize; indexCount += sprite.indices.length; } @@ -327,7 +330,7 @@ { this.vaoMax++; /* eslint-disable max-len */ - this.vaos[this.vertexCount] = new BatchGeometry(); + this.vaos[this.vertexCount] = new (this.geometryClass)(); } this.vaos[this.vertexCount]._buffer.update(buffer.vertices, 0); @@ -391,7 +394,7 @@ packGeometry(element, float32View, uint32View, indexBuffer, index, indexCount) { - const p = index / this.vertSize;// float32View.length / 6 / 2; + const p = index / this.vertexSize;// float32View.length / 6 / 2; const uvs = element.uvs; const indicies = element.indices;// geometry.getIndex().data;// indicies; const vertexData = element.vertexData; diff --git a/packages/core/src/batch/BatchShaderGenerator.js b/packages/core/src/batch/BatchShaderGenerator.js new file mode 100644 index 0000000..4abd176 --- /dev/null +++ b/packages/core/src/batch/BatchShaderGenerator.js @@ -0,0 +1,107 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import UniformGroup from '../shader/UniformGroup'; +import { Matrix } from '@pixi/math'; + +/** + * Helper that generates batching multi-texture shader. Use it with your new BatchRenderer + * + * @class + * @memberof PIXI + */ +export default class BatchShaderGenerator +{ + /** + * @param {string} vertexSrc - Vertex shader + * @param {string} fragTemplate - Fragment shader template + */ + constructor(vertexSrc, fragTemplate) + { + /** + * Reference to the vertex shader source. + * + * @member {string} + */ + this.vertexSrc = vertexSrc; + + /** + * Reference to the fragement shader template. Must contain "%count%" and "%forloop%". + * + * @member {string} + */ + this.fragTemplate = fragTemplate; + + this.programCache = {}; + this.defaultGroupCache = {}; + + if (fragTemplate.indexOf('%count%') < 0) + { + throw new Error('Fragment template must contain "%count%".'); + } + + if (fragTemplate.indexOf('%forloop%') < 0) + { + throw new Error('Fragment template must contain "%forloop%".'); + } + } + + generateShader(maxTextures) + { + if (!this.programCache[maxTextures]) + { + const sampleValues = new Int32Array(maxTextures); + + for (let i = 0; i < maxTextures; i++) + { + sampleValues[i] = i; + } + + this.defaultGroupCache[maxTextures] = UniformGroup.from({ uSamplers: sampleValues }, true); + + let fragmentSrc = this.fragTemplate; + + fragmentSrc = fragmentSrc.replace(/%count%/gi, `${maxTextures}`); + fragmentSrc = fragmentSrc.replace(/%forloop%/gi, this.generateSampleSrc(maxTextures)); + + this.programCache[maxTextures] = new Program(this.vertexSrc, fragmentSrc); + } + + const uniforms = { + tint: new Float32Array([1, 1, 1, 1]), + translationMatrix: new Matrix(), + default: this.defaultGroupCache[maxTextures], + }; + + return new Shader(this.programCache[maxTextures], uniforms); + } + + generateSampleSrc(maxTextures) + { + let src = ''; + + src += '\n'; + src += '\n'; + + for (let i = 0; i < maxTextures; i++) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxTextures - 1) + { + src += `if(vTextureId < ${i}.5)`; + } + + src += '\n{'; + src += `\n\tcolor = texture2D(uSamplers[${i}], vTextureCoord);`; + src += '\n}'; + } + + src += '\n'; + src += '\n'; + + return src; + } +} diff --git a/packages/core/src/batch/generateMultiTextureShader.js b/packages/core/src/batch/generateMultiTextureShader.js deleted file mode 100644 index 20d534d..0000000 --- a/packages/core/src/batch/generateMultiTextureShader.js +++ /dev/null @@ -1,84 +0,0 @@ -import Shader from '../shader/Shader'; -import Program from '../shader/Program'; -import UniformGroup from '../shader/UniformGroup'; - -import vertex from './texture.vert'; -import { Matrix } from '@pixi/math'; - -const fragTemplate = [ - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - 'varying float vTextureId;', - 'uniform sampler2D uSamplers[%count%];', - - 'void main(void){', - 'vec4 color;', - '%forloop%', - 'gl_FragColor = color * vColor;', - '}', -].join('\n'); - -const defaultGroupCache = {}; -const programCache = {}; - -export default function generateMultiTextureShader(gl, maxTextures) -{ - if (!programCache[maxTextures]) - { - const sampleValues = new Int32Array(maxTextures); - - for (let i = 0; i < maxTextures; i++) - { - sampleValues[i] = i; - } - - defaultGroupCache[maxTextures] = UniformGroup.from({ uSamplers: sampleValues }, true); - - let fragmentSrc = fragTemplate; - - fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); - fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); - - programCache[maxTextures] = new Program(vertex, fragmentSrc); - } - - const uniforms = { - tint: new Float32Array([1, 1, 1, 1]), - translationMatrix: new Matrix(), - default: defaultGroupCache[maxTextures], - }; - - const shader = new Shader(programCache[maxTextures], uniforms); - - return shader; -} - -function generateSampleSrc(maxTextures) -{ - let src = ''; - - src += '\n'; - src += '\n'; - - for (let i = 0; i < maxTextures; i++) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxTextures - 1) - { - src += `if(vTextureId < ${i}.5)`; - } - - src += '\n{'; - src += `\n\tcolor = texture2D(uSamplers[${i}], vTextureCoord);`; - src += '\n}'; - } - - src += '\n'; - src += '\n'; - - return src; -} diff --git a/packages/core/src/batch/texture.frag b/packages/core/src/batch/texture.frag new file mode 100644 index 0000000..6b3847b --- /dev/null +++ b/packages/core/src/batch/texture.frag @@ -0,0 +1,10 @@ +varying vec2 vTextureCoord; +varying vec4 vColor; +varying float vTextureId; +uniform sampler2D uSamplers[%count%]; + +void main(void){ + vec4 color; + %forloop% + gl_FragColor = color * vColor; +} diff --git a/packages/core/src/batch/BatchPluginFactory.js b/packages/core/src/batch/BatchPluginFactory.js new file mode 100644 index 0000000..22bd0ba --- /dev/null +++ b/packages/core/src/batch/BatchPluginFactory.js @@ -0,0 +1,94 @@ +import BatchShaderGenerator from './BatchShaderGenerator'; +import BatchGeometry from './BatchGeometry'; +import BaseBatchRenderer from './BatchRenderer'; + +import defaultVertex from './texture.vert'; +import defaultFragment from './texture.frag'; + +/** + * Used to create new, custom BatchRenderer plugins for the Renderer. + * @example + * const fragment = ` + * varying vec2 vTextureCoord; + * varying vec4 vColor; + * varying float vTextureId; + * uniform sampler2D uSamplers[%count%]; + * + * void main(void){ + * vec4 color; + * %forloop% + * gl_FragColor = vColor * vec4(color.a - color.rgb, color.a); + * } + * `; + * const InvertBatchRenderer = PIXI.BatchPluginFactory.create({ fragment }); + * PIXI.Renderer.registerPlugin('invert', InvertBatchRenderer); + * const sprite = new PIXI.Sprite(); + * sprite.pluginName = 'invert'; + * + * @class + * @memberof PIXI + */ +export class BatchPluginFactory +{ + /** + * Create a new BatchRenderer plugin for Renderer. this convenience can provide an easy way + * to extend BatchRenderer with all the necessary pieces. + * + * @static + * @param {object} [options] + * @param {string} [options.vertex=PIXI.BatchPluginFactory.defaultVertexSrc] - Vertex shader source + * @param {string} [options.fragment=PIXI.BatchPluginFactory.defaultFragmentTemplate] - Fragment shader template + * @param {number} [options.vertexSize=6] - Vertex size + * @param {object} [options.geometryClass=PIXI.BatchGeometry] + * @return {PIXI.BatchRenderer} New batch renderer plugin. + */ + static create(options) + { + const { vertex, fragment, vertexSize, geometryClass } = Object.assign({ + vertex: defaultVertex, + fragment: defaultFragment, + geometryClass: BatchGeometry, + vertexSize: 6, + }, options); + + return class BatchPlugin extends BaseBatchRenderer + { + constructor(renderer) + { + super(renderer); + + this.shaderGenerator = new BatchShaderGenerator(vertex, fragment); + this.geometryClass = geometryClass; + this.vertexSize = vertexSize; + } + }; + } + + /** + * The default vertex shader source + * + * @static + * @type {string} + * @constant + */ + static get defaultVertexSrc() + { + return defaultVertex; + } + + /** + * The default fragment shader source + * + * @static + * @type {string} + * @constant + */ + static get defaultFragmentTemplate() + { + return defaultFragment; + } +} + +// Setup the default BatchRenderer plugin, this is what +// we'll actually export at the root level +export const BatchRenderer = BatchPluginFactory.create(); diff --git a/packages/core/src/batch/BatchRenderer.js b/packages/core/src/batch/BatchRenderer.js index bddddca..690650f 100644 --- a/packages/core/src/batch/BatchRenderer.js +++ b/packages/core/src/batch/BatchRenderer.js @@ -1,16 +1,11 @@ -import BatchGeometry from './BatchGeometry'; import BatchDrawCall from './BatchDrawCall'; import BaseTexture from '../textures/BaseTexture'; - import State from '../state/State'; import ObjectRenderer from './ObjectRenderer'; import checkMaxIfStatementsInShader from '../shader/utils/checkMaxIfStatementsInShader'; - import { settings } from '@pixi/settings'; import { premultiplyBlendMode, premultiplyTint, nextPow2, log2 } from '@pixi/utils'; - import BatchBuffer from './BatchBuffer'; -import generateMultiTextureShader from './generateMultiTextureShader'; import { ENV } from '@pixi/constants'; /** @@ -31,21 +26,6 @@ super(renderer); /** - * Number of values sent in the vertex buffer. - * aVertexPosition(2), aTextureCoord(1), aColor(1), aTextureId(1) = 5 - * - * @member {number} - */ - this.vertSize = 6; - - /** - * 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} @@ -79,7 +59,7 @@ * 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 rendered. * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} + * @member {PIXI.Shader} */ this.shader = null; @@ -100,6 +80,29 @@ this.renderer.on('prerender', this.onPrerender, this); this.state = State.for2d(); + + /** + * MultiTexture shader generator. + * + * @member {PIXI.BatchShaderGenerator} + */ + this.shaderGenerator = null; + + /** + * The class we use to create geometries. + * Please override it in constructor + * @member {object} + * @default PIXI.BatchGeometry + */ + this.geometryClass = null; + + /** + * Number of values sent in the vertex buffer. + * aVertexPosition(2), aTextureCoord(1), aColor(1), aTextureId(1) = 5 + * + * @member {number} vertSize + */ + this.vertexSize = null; } /** @@ -122,15 +125,14 @@ this.MAX_TEXTURES = checkMaxIfStatementsInShader(this.MAX_TEXTURES, gl); } - // generate generateMultiTextureProgram, may be a better move? - this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); + this.shader = this.shaderGenerator.generateShader(this.MAX_TEXTURES); // 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. for (let i = 0; i < this.vaoMax; i++) { /* eslint-disable max-len */ - this.vaos[i] = new BatchGeometry(); + this.vaos[i] = new (this.geometryClass)(); } } @@ -204,7 +206,7 @@ if (!buffer) { - this.aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertByteSize); + this.aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertexSize * 4); } return buffer; @@ -223,6 +225,7 @@ const gl = this.renderer.gl; const MAX_TEXTURES = this.MAX_TEXTURES; + const vertSize = this.vertexSize; const buffer = this.getAttributeBuffer(this.currentSize); const indexBuffer = this.getIndexBuffer(this.currentIndexSize); @@ -309,7 +312,7 @@ this.packGeometry(sprite, float32View, uint32View, indexBuffer, index, indexCount);// argb, nextTexture._id, float32View, uint32View, indexBuffer, index, indexCount); // push a graphics.. - index += (sprite.vertexData.length / 2) * this.vertSize; + index += (sprite.vertexData.length / 2) * vertSize; indexCount += sprite.indices.length; } @@ -327,7 +330,7 @@ { this.vaoMax++; /* eslint-disable max-len */ - this.vaos[this.vertexCount] = new BatchGeometry(); + this.vaos[this.vertexCount] = new (this.geometryClass)(); } this.vaos[this.vertexCount]._buffer.update(buffer.vertices, 0); @@ -391,7 +394,7 @@ packGeometry(element, float32View, uint32View, indexBuffer, index, indexCount) { - const p = index / this.vertSize;// float32View.length / 6 / 2; + const p = index / this.vertexSize;// float32View.length / 6 / 2; const uvs = element.uvs; const indicies = element.indices;// geometry.getIndex().data;// indicies; const vertexData = element.vertexData; diff --git a/packages/core/src/batch/BatchShaderGenerator.js b/packages/core/src/batch/BatchShaderGenerator.js new file mode 100644 index 0000000..4abd176 --- /dev/null +++ b/packages/core/src/batch/BatchShaderGenerator.js @@ -0,0 +1,107 @@ +import Shader from '../shader/Shader'; +import Program from '../shader/Program'; +import UniformGroup from '../shader/UniformGroup'; +import { Matrix } from '@pixi/math'; + +/** + * Helper that generates batching multi-texture shader. Use it with your new BatchRenderer + * + * @class + * @memberof PIXI + */ +export default class BatchShaderGenerator +{ + /** + * @param {string} vertexSrc - Vertex shader + * @param {string} fragTemplate - Fragment shader template + */ + constructor(vertexSrc, fragTemplate) + { + /** + * Reference to the vertex shader source. + * + * @member {string} + */ + this.vertexSrc = vertexSrc; + + /** + * Reference to the fragement shader template. Must contain "%count%" and "%forloop%". + * + * @member {string} + */ + this.fragTemplate = fragTemplate; + + this.programCache = {}; + this.defaultGroupCache = {}; + + if (fragTemplate.indexOf('%count%') < 0) + { + throw new Error('Fragment template must contain "%count%".'); + } + + if (fragTemplate.indexOf('%forloop%') < 0) + { + throw new Error('Fragment template must contain "%forloop%".'); + } + } + + generateShader(maxTextures) + { + if (!this.programCache[maxTextures]) + { + const sampleValues = new Int32Array(maxTextures); + + for (let i = 0; i < maxTextures; i++) + { + sampleValues[i] = i; + } + + this.defaultGroupCache[maxTextures] = UniformGroup.from({ uSamplers: sampleValues }, true); + + let fragmentSrc = this.fragTemplate; + + fragmentSrc = fragmentSrc.replace(/%count%/gi, `${maxTextures}`); + fragmentSrc = fragmentSrc.replace(/%forloop%/gi, this.generateSampleSrc(maxTextures)); + + this.programCache[maxTextures] = new Program(this.vertexSrc, fragmentSrc); + } + + const uniforms = { + tint: new Float32Array([1, 1, 1, 1]), + translationMatrix: new Matrix(), + default: this.defaultGroupCache[maxTextures], + }; + + return new Shader(this.programCache[maxTextures], uniforms); + } + + generateSampleSrc(maxTextures) + { + let src = ''; + + src += '\n'; + src += '\n'; + + for (let i = 0; i < maxTextures; i++) + { + if (i > 0) + { + src += '\nelse '; + } + + if (i < maxTextures - 1) + { + src += `if(vTextureId < ${i}.5)`; + } + + src += '\n{'; + src += `\n\tcolor = texture2D(uSamplers[${i}], vTextureCoord);`; + src += '\n}'; + } + + src += '\n'; + src += '\n'; + + return src; + } +} diff --git a/packages/core/src/batch/generateMultiTextureShader.js b/packages/core/src/batch/generateMultiTextureShader.js deleted file mode 100644 index 20d534d..0000000 --- a/packages/core/src/batch/generateMultiTextureShader.js +++ /dev/null @@ -1,84 +0,0 @@ -import Shader from '../shader/Shader'; -import Program from '../shader/Program'; -import UniformGroup from '../shader/UniformGroup'; - -import vertex from './texture.vert'; -import { Matrix } from '@pixi/math'; - -const fragTemplate = [ - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - 'varying float vTextureId;', - 'uniform sampler2D uSamplers[%count%];', - - 'void main(void){', - 'vec4 color;', - '%forloop%', - 'gl_FragColor = color * vColor;', - '}', -].join('\n'); - -const defaultGroupCache = {}; -const programCache = {}; - -export default function generateMultiTextureShader(gl, maxTextures) -{ - if (!programCache[maxTextures]) - { - const sampleValues = new Int32Array(maxTextures); - - for (let i = 0; i < maxTextures; i++) - { - sampleValues[i] = i; - } - - defaultGroupCache[maxTextures] = UniformGroup.from({ uSamplers: sampleValues }, true); - - let fragmentSrc = fragTemplate; - - fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); - fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); - - programCache[maxTextures] = new Program(vertex, fragmentSrc); - } - - const uniforms = { - tint: new Float32Array([1, 1, 1, 1]), - translationMatrix: new Matrix(), - default: defaultGroupCache[maxTextures], - }; - - const shader = new Shader(programCache[maxTextures], uniforms); - - return shader; -} - -function generateSampleSrc(maxTextures) -{ - let src = ''; - - src += '\n'; - src += '\n'; - - for (let i = 0; i < maxTextures; i++) - { - if (i > 0) - { - src += '\nelse '; - } - - if (i < maxTextures - 1) - { - src += `if(vTextureId < ${i}.5)`; - } - - src += '\n{'; - src += `\n\tcolor = texture2D(uSamplers[${i}], vTextureCoord);`; - src += '\n}'; - } - - src += '\n'; - src += '\n'; - - return src; -} diff --git a/packages/core/src/batch/texture.frag b/packages/core/src/batch/texture.frag new file mode 100644 index 0000000..6b3847b --- /dev/null +++ b/packages/core/src/batch/texture.frag @@ -0,0 +1,10 @@ +varying vec2 vTextureCoord; +varying vec4 vColor; +varying float vTextureId; +uniform sampler2D uSamplers[%count%]; + +void main(void){ + vec4 color; + %forloop% + gl_FragColor = color * vColor; +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index e7bee54..448e3d1 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -22,10 +22,10 @@ export { default as TextureUvs } from './textures/TextureUvs'; export { default as State } from './state/State'; export { default as ObjectRenderer } from './batch/ObjectRenderer'; -export { default as BatchRenderer } from './batch/BatchRenderer'; +export * from './batch/BatchPluginFactory'; +export { default as BatchShaderGenerator } from './batch/BatchShaderGenerator'; export { default as BatchGeometry } from './batch/BatchGeometry'; export { default as BatchDrawCall } from './batch/BatchDrawCall'; -export { default as generateMultiTextureShader } from './batch/generateMultiTextureShader'; export { default as Quad } from './utils/Quad'; export { default as QuadUv } from './utils/QuadUv'; export { default as checkMaxIfStatementsInShader } from './shader/utils/checkMaxIfStatementsInShader';