diff --git a/packages/core/src/batch/AbstractBatchRenderer.js b/packages/core/src/batch/AbstractBatchRenderer.js index 340a382..72a5457 100644 --- a/packages/core/src/batch/AbstractBatchRenderer.js +++ b/packages/core/src/batch/AbstractBatchRenderer.js @@ -9,6 +9,18 @@ import { ENV } from '@pixi/constants'; /** + * Sizes of built-in attributes provided by `BatchRenderer`, + * which includes the required `aTextureId` attribute. + * + * @type Map + * @see {AbstractBatchRenderer#vertexSizeOf} + */ +const builtinAttributeSizes = { + aColor: 1, + aTextureId: 1, +}; + +/** * Renderer dedicated to drawing and batching sprites. * * This is the default batch renderer. It buffers objects @@ -60,15 +72,57 @@ this.geometryClass = null; /** + * Array of attribute definitions that are described + * below. These are used to pass attribute data + * from your objects to the vertex shader. + * + * An attribute definition is an object with the + * following properties: + * + * | Property | Description | + * |------------|-----------------------------------------------| + * | property | Property name in objects for attribute data | + * | identifier | Name of attribute variable used in the shader | + * | size | Size of each attribute element in floats | + * + * `BatchRenderer` provides a few built-in attributes + * that you can use like `aColor`. Instead of the + * attribute definition, just give the identifier to + * use them. + * + * The `attribute float aTextureId` attribute should + * _always_ be present in your shader. See + * `PIXI.BatchShaderGenerator` for more details. + * + * NOTE: It is expected that the attribute buffers + * store an equal number of elements. For example, + * `object.vertexData.length = object.uvs.length`. Otherwise, + * wrapping will occur so that the attribute data is repeated + * for each vertex. + * + * @default + * | Index | AttributeDefinition | + * |-------|--------------------------------------------------------------------| + * | 0 | `{ property: vertexData, identifier: 'aVertexPosition', size: 2 }` | + * | 1 | `{ property: uvs, identifier: 'aTextureCoord', size: 2 }` | + * | 2 | `'aColor'` | + * | 3 | `'aTextureId'` // mandatory | + * + * @readonly + */ + this.attributeDefinitions = null; + + /** * Size of data being buffered per vertex in the * attribute buffers (in floats). By default, the * batch-renderer plugin uses 6: * - * | aVertexPosition | 2 | - * |-----------------|---| - * | aTextureCoords | 2 | - * | aColor | 1 | - * | aTextureId | 1 | + * | Attribute | Size | + * |-----------------|------| + * | aVertexPosition | 2 | + * | aTextureCoords | 2 | + * | aColor | 1 | + * | aTextureId | 1 | * * @member {number} vertexSize * @readonly @@ -593,12 +647,10 @@ } /** - * Takes the four batching parameters of `element`, interleaves - * and pushes them into the batching attribute/index buffers given. - * - * It uses these properties: `vertexData` `uvs`, `textureId` and - * `indicies`. It also uses the "tint" of the base-texture, if - * present. + * Takes all the attributes sources of the element being + * drawn, interleaves them, and appends them to the + * attribute buffer. It also appends the indices of the + * element into the index buffer. * * @param {PIXI.Sprite} element - element being rendered * @param {Float32Array} float32View - float32-view of the attribute buffer @@ -610,32 +662,119 @@ packInterleavedGeometry(element, float32View, uint32View, indexBuffer, aIndex, iIndex) { - const p = aIndex / this.vertexSize; - const uvs = element.uvs; + const packedVerticies = aIndex / this.vertexSize; const indicies = element.indices; - const vertexData = element.vertexData; const textureId = element._texture.baseTexture._id; - const alpha = Math.min(element.worldAlpha, 1.0); - const argb = (alpha < 1.0 - && element._texture.baseTexture.premultiplyAlpha) - ? premultiplyTint(element._tintRGB, alpha) - : element._tintRGB + (alpha * 255 << 24); + const attributeDefinitions = this.attributeDefinitions; + const attributeSources = []; + let highestAttributeLength = 0; - // lets not worry about tint! for now.. - for (let i = 0; i < vertexData.length; i += 2) + for (let i = 0; i < attributeDefinitions.length; i++) { - float32View[aIndex++] = vertexData[i]; - float32View[aIndex++] = vertexData[i + 1]; - float32View[aIndex++] = uvs[i]; - float32View[aIndex++] = uvs[i + 1]; - uint32View[aIndex++] = argb; - float32View[aIndex++] = textureId; + const attribute = attributeDefinitions[i]; + + if (typeof attributeDefinitions[i] !== 'string') + { + const source = element[attributeDefinitions[i].property]; + + attributeSources.push(source); + highestAttributeLength = Math.max( + highestAttributeLength, source.length / attribute.size); + } + else + { + switch (attributeDefinitions[i]) + { + case 'aColor': + { + const alpha = Math.min(element.worldAlpha, 1.0); + const argb = (alpha < 1.0 + && element._texture.baseTexture.premultiplyAlpha) + ? premultiplyTint(element._tintRGB, alpha) + : element._tintRGB + (alpha * 255 << 24); + + attributeSources.push([Math.round(argb)]); + highestAttributeLength + = Math.max(highestAttributeLength, 1); + break; + } + case 'aTextureId': + { + attributeSources.push(null); + break; + } + default: + { + throw new Error(`Unknown built-in attribute ` + + `given to AbstractBatchRenderer: ` + + `${attributeDefinitions[i]}`); + } + } + } + } + + for (let d = 0; d < attributeDefinitions.length; d++) + { + attributeDefinitions[d].offset = 0; + } + + for (let i = 0; i < highestAttributeLength; i++) + { + for (let s = 0; s < attributeSources.length; s++) + { + const attribute = attributeDefinitions[s]; + const source = attributeSources[s]; + + if (!source)// Only aTextureId has no source! + { + float32View[aIndex++] = textureId; + continue; + } + + for (let float = 0; float < attribute.size; float++) + { + float32View[aIndex++] = source[attribute.offset++ % source.length]; + } + } } for (let i = 0; i < indicies.length; i++) { - indexBuffer[iIndex++] = p + indicies[i]; + indexBuffer[iIndex++] = packedVerticies + indicies[i]; } } + + /** + * Calculates the vertex size for the given attribute + * definitions. It also accounts for built-in attributes. + * + * @param {Array} attributeDefinitions - attribute definitions + * @return sum of all attribute sizes + */ + static vertexSizeOf(attributeDefinitions) + { + let vertexSize = 0; + + for (let d = 0; d < attributeDefinitions.length; d++) + { + const definition = attributeDefinitions[d]; + + if (typeof definition !== 'string') + { + vertexSize += attributeDefinitions[d].size; + } + else + { + if (!builtinAttributeSizes[definition]) + { + throw new Error(`${definition} is not a builtin attribute!`); + } + + vertexSize += builtinAttributeSizes[definition]; + } + } + + return vertexSize; + } } diff --git a/packages/core/src/batch/AbstractBatchRenderer.js b/packages/core/src/batch/AbstractBatchRenderer.js index 340a382..72a5457 100644 --- a/packages/core/src/batch/AbstractBatchRenderer.js +++ b/packages/core/src/batch/AbstractBatchRenderer.js @@ -9,6 +9,18 @@ import { ENV } from '@pixi/constants'; /** + * Sizes of built-in attributes provided by `BatchRenderer`, + * which includes the required `aTextureId` attribute. + * + * @type Map + * @see {AbstractBatchRenderer#vertexSizeOf} + */ +const builtinAttributeSizes = { + aColor: 1, + aTextureId: 1, +}; + +/** * Renderer dedicated to drawing and batching sprites. * * This is the default batch renderer. It buffers objects @@ -60,15 +72,57 @@ this.geometryClass = null; /** + * Array of attribute definitions that are described + * below. These are used to pass attribute data + * from your objects to the vertex shader. + * + * An attribute definition is an object with the + * following properties: + * + * | Property | Description | + * |------------|-----------------------------------------------| + * | property | Property name in objects for attribute data | + * | identifier | Name of attribute variable used in the shader | + * | size | Size of each attribute element in floats | + * + * `BatchRenderer` provides a few built-in attributes + * that you can use like `aColor`. Instead of the + * attribute definition, just give the identifier to + * use them. + * + * The `attribute float aTextureId` attribute should + * _always_ be present in your shader. See + * `PIXI.BatchShaderGenerator` for more details. + * + * NOTE: It is expected that the attribute buffers + * store an equal number of elements. For example, + * `object.vertexData.length = object.uvs.length`. Otherwise, + * wrapping will occur so that the attribute data is repeated + * for each vertex. + * + * @default + * | Index | AttributeDefinition | + * |-------|--------------------------------------------------------------------| + * | 0 | `{ property: vertexData, identifier: 'aVertexPosition', size: 2 }` | + * | 1 | `{ property: uvs, identifier: 'aTextureCoord', size: 2 }` | + * | 2 | `'aColor'` | + * | 3 | `'aTextureId'` // mandatory | + * + * @readonly + */ + this.attributeDefinitions = null; + + /** * Size of data being buffered per vertex in the * attribute buffers (in floats). By default, the * batch-renderer plugin uses 6: * - * | aVertexPosition | 2 | - * |-----------------|---| - * | aTextureCoords | 2 | - * | aColor | 1 | - * | aTextureId | 1 | + * | Attribute | Size | + * |-----------------|------| + * | aVertexPosition | 2 | + * | aTextureCoords | 2 | + * | aColor | 1 | + * | aTextureId | 1 | * * @member {number} vertexSize * @readonly @@ -593,12 +647,10 @@ } /** - * Takes the four batching parameters of `element`, interleaves - * and pushes them into the batching attribute/index buffers given. - * - * It uses these properties: `vertexData` `uvs`, `textureId` and - * `indicies`. It also uses the "tint" of the base-texture, if - * present. + * Takes all the attributes sources of the element being + * drawn, interleaves them, and appends them to the + * attribute buffer. It also appends the indices of the + * element into the index buffer. * * @param {PIXI.Sprite} element - element being rendered * @param {Float32Array} float32View - float32-view of the attribute buffer @@ -610,32 +662,119 @@ packInterleavedGeometry(element, float32View, uint32View, indexBuffer, aIndex, iIndex) { - const p = aIndex / this.vertexSize; - const uvs = element.uvs; + const packedVerticies = aIndex / this.vertexSize; const indicies = element.indices; - const vertexData = element.vertexData; const textureId = element._texture.baseTexture._id; - const alpha = Math.min(element.worldAlpha, 1.0); - const argb = (alpha < 1.0 - && element._texture.baseTexture.premultiplyAlpha) - ? premultiplyTint(element._tintRGB, alpha) - : element._tintRGB + (alpha * 255 << 24); + const attributeDefinitions = this.attributeDefinitions; + const attributeSources = []; + let highestAttributeLength = 0; - // lets not worry about tint! for now.. - for (let i = 0; i < vertexData.length; i += 2) + for (let i = 0; i < attributeDefinitions.length; i++) { - float32View[aIndex++] = vertexData[i]; - float32View[aIndex++] = vertexData[i + 1]; - float32View[aIndex++] = uvs[i]; - float32View[aIndex++] = uvs[i + 1]; - uint32View[aIndex++] = argb; - float32View[aIndex++] = textureId; + const attribute = attributeDefinitions[i]; + + if (typeof attributeDefinitions[i] !== 'string') + { + const source = element[attributeDefinitions[i].property]; + + attributeSources.push(source); + highestAttributeLength = Math.max( + highestAttributeLength, source.length / attribute.size); + } + else + { + switch (attributeDefinitions[i]) + { + case 'aColor': + { + const alpha = Math.min(element.worldAlpha, 1.0); + const argb = (alpha < 1.0 + && element._texture.baseTexture.premultiplyAlpha) + ? premultiplyTint(element._tintRGB, alpha) + : element._tintRGB + (alpha * 255 << 24); + + attributeSources.push([Math.round(argb)]); + highestAttributeLength + = Math.max(highestAttributeLength, 1); + break; + } + case 'aTextureId': + { + attributeSources.push(null); + break; + } + default: + { + throw new Error(`Unknown built-in attribute ` + + `given to AbstractBatchRenderer: ` + + `${attributeDefinitions[i]}`); + } + } + } + } + + for (let d = 0; d < attributeDefinitions.length; d++) + { + attributeDefinitions[d].offset = 0; + } + + for (let i = 0; i < highestAttributeLength; i++) + { + for (let s = 0; s < attributeSources.length; s++) + { + const attribute = attributeDefinitions[s]; + const source = attributeSources[s]; + + if (!source)// Only aTextureId has no source! + { + float32View[aIndex++] = textureId; + continue; + } + + for (let float = 0; float < attribute.size; float++) + { + float32View[aIndex++] = source[attribute.offset++ % source.length]; + } + } } for (let i = 0; i < indicies.length; i++) { - indexBuffer[iIndex++] = p + indicies[i]; + indexBuffer[iIndex++] = packedVerticies + indicies[i]; } } + + /** + * Calculates the vertex size for the given attribute + * definitions. It also accounts for built-in attributes. + * + * @param {Array} attributeDefinitions - attribute definitions + * @return sum of all attribute sizes + */ + static vertexSizeOf(attributeDefinitions) + { + let vertexSize = 0; + + for (let d = 0; d < attributeDefinitions.length; d++) + { + const definition = attributeDefinitions[d]; + + if (typeof definition !== 'string') + { + vertexSize += attributeDefinitions[d].size; + } + else + { + if (!builtinAttributeSizes[definition]) + { + throw new Error(`${definition} is not a builtin attribute!`); + } + + vertexSize += builtinAttributeSizes[definition]; + } + } + + return vertexSize; + } } diff --git a/packages/core/src/batch/BatchGeometry.js b/packages/core/src/batch/BatchGeometry.js index 6275f8c..5b8afc4 100644 --- a/packages/core/src/batch/BatchGeometry.js +++ b/packages/core/src/batch/BatchGeometry.js @@ -3,7 +3,8 @@ import Buffer from '../geometry/Buffer'; /** - * Geometry used to batch standard PIXI content (e.g. Mesh, Sprite, Graphics objects). + * Geometry used to batch standard PIXI content (e.g. Mesh, Sprite, + * Graphics objects). * * @class * @memberof PIXI @@ -34,6 +35,7 @@ */ this._indexBuffer = new Buffer(null, _static, true); + /* These are automatically interleaved by GeometrySystem. */ this.addAttribute('aVertexPosition', this._buffer, 2, false, TYPES.FLOAT) .addAttribute('aTextureCoord', this._buffer, 2, false, TYPES.FLOAT) .addAttribute('aColor', this._buffer, 4, true, TYPES.UNSIGNED_BYTE) diff --git a/packages/core/src/batch/AbstractBatchRenderer.js b/packages/core/src/batch/AbstractBatchRenderer.js index 340a382..72a5457 100644 --- a/packages/core/src/batch/AbstractBatchRenderer.js +++ b/packages/core/src/batch/AbstractBatchRenderer.js @@ -9,6 +9,18 @@ import { ENV } from '@pixi/constants'; /** + * Sizes of built-in attributes provided by `BatchRenderer`, + * which includes the required `aTextureId` attribute. + * + * @type Map + * @see {AbstractBatchRenderer#vertexSizeOf} + */ +const builtinAttributeSizes = { + aColor: 1, + aTextureId: 1, +}; + +/** * Renderer dedicated to drawing and batching sprites. * * This is the default batch renderer. It buffers objects @@ -60,15 +72,57 @@ this.geometryClass = null; /** + * Array of attribute definitions that are described + * below. These are used to pass attribute data + * from your objects to the vertex shader. + * + * An attribute definition is an object with the + * following properties: + * + * | Property | Description | + * |------------|-----------------------------------------------| + * | property | Property name in objects for attribute data | + * | identifier | Name of attribute variable used in the shader | + * | size | Size of each attribute element in floats | + * + * `BatchRenderer` provides a few built-in attributes + * that you can use like `aColor`. Instead of the + * attribute definition, just give the identifier to + * use them. + * + * The `attribute float aTextureId` attribute should + * _always_ be present in your shader. See + * `PIXI.BatchShaderGenerator` for more details. + * + * NOTE: It is expected that the attribute buffers + * store an equal number of elements. For example, + * `object.vertexData.length = object.uvs.length`. Otherwise, + * wrapping will occur so that the attribute data is repeated + * for each vertex. + * + * @default + * | Index | AttributeDefinition | + * |-------|--------------------------------------------------------------------| + * | 0 | `{ property: vertexData, identifier: 'aVertexPosition', size: 2 }` | + * | 1 | `{ property: uvs, identifier: 'aTextureCoord', size: 2 }` | + * | 2 | `'aColor'` | + * | 3 | `'aTextureId'` // mandatory | + * + * @readonly + */ + this.attributeDefinitions = null; + + /** * Size of data being buffered per vertex in the * attribute buffers (in floats). By default, the * batch-renderer plugin uses 6: * - * | aVertexPosition | 2 | - * |-----------------|---| - * | aTextureCoords | 2 | - * | aColor | 1 | - * | aTextureId | 1 | + * | Attribute | Size | + * |-----------------|------| + * | aVertexPosition | 2 | + * | aTextureCoords | 2 | + * | aColor | 1 | + * | aTextureId | 1 | * * @member {number} vertexSize * @readonly @@ -593,12 +647,10 @@ } /** - * Takes the four batching parameters of `element`, interleaves - * and pushes them into the batching attribute/index buffers given. - * - * It uses these properties: `vertexData` `uvs`, `textureId` and - * `indicies`. It also uses the "tint" of the base-texture, if - * present. + * Takes all the attributes sources of the element being + * drawn, interleaves them, and appends them to the + * attribute buffer. It also appends the indices of the + * element into the index buffer. * * @param {PIXI.Sprite} element - element being rendered * @param {Float32Array} float32View - float32-view of the attribute buffer @@ -610,32 +662,119 @@ packInterleavedGeometry(element, float32View, uint32View, indexBuffer, aIndex, iIndex) { - const p = aIndex / this.vertexSize; - const uvs = element.uvs; + const packedVerticies = aIndex / this.vertexSize; const indicies = element.indices; - const vertexData = element.vertexData; const textureId = element._texture.baseTexture._id; - const alpha = Math.min(element.worldAlpha, 1.0); - const argb = (alpha < 1.0 - && element._texture.baseTexture.premultiplyAlpha) - ? premultiplyTint(element._tintRGB, alpha) - : element._tintRGB + (alpha * 255 << 24); + const attributeDefinitions = this.attributeDefinitions; + const attributeSources = []; + let highestAttributeLength = 0; - // lets not worry about tint! for now.. - for (let i = 0; i < vertexData.length; i += 2) + for (let i = 0; i < attributeDefinitions.length; i++) { - float32View[aIndex++] = vertexData[i]; - float32View[aIndex++] = vertexData[i + 1]; - float32View[aIndex++] = uvs[i]; - float32View[aIndex++] = uvs[i + 1]; - uint32View[aIndex++] = argb; - float32View[aIndex++] = textureId; + const attribute = attributeDefinitions[i]; + + if (typeof attributeDefinitions[i] !== 'string') + { + const source = element[attributeDefinitions[i].property]; + + attributeSources.push(source); + highestAttributeLength = Math.max( + highestAttributeLength, source.length / attribute.size); + } + else + { + switch (attributeDefinitions[i]) + { + case 'aColor': + { + const alpha = Math.min(element.worldAlpha, 1.0); + const argb = (alpha < 1.0 + && element._texture.baseTexture.premultiplyAlpha) + ? premultiplyTint(element._tintRGB, alpha) + : element._tintRGB + (alpha * 255 << 24); + + attributeSources.push([Math.round(argb)]); + highestAttributeLength + = Math.max(highestAttributeLength, 1); + break; + } + case 'aTextureId': + { + attributeSources.push(null); + break; + } + default: + { + throw new Error(`Unknown built-in attribute ` + + `given to AbstractBatchRenderer: ` + + `${attributeDefinitions[i]}`); + } + } + } + } + + for (let d = 0; d < attributeDefinitions.length; d++) + { + attributeDefinitions[d].offset = 0; + } + + for (let i = 0; i < highestAttributeLength; i++) + { + for (let s = 0; s < attributeSources.length; s++) + { + const attribute = attributeDefinitions[s]; + const source = attributeSources[s]; + + if (!source)// Only aTextureId has no source! + { + float32View[aIndex++] = textureId; + continue; + } + + for (let float = 0; float < attribute.size; float++) + { + float32View[aIndex++] = source[attribute.offset++ % source.length]; + } + } } for (let i = 0; i < indicies.length; i++) { - indexBuffer[iIndex++] = p + indicies[i]; + indexBuffer[iIndex++] = packedVerticies + indicies[i]; } } + + /** + * Calculates the vertex size for the given attribute + * definitions. It also accounts for built-in attributes. + * + * @param {Array} attributeDefinitions - attribute definitions + * @return sum of all attribute sizes + */ + static vertexSizeOf(attributeDefinitions) + { + let vertexSize = 0; + + for (let d = 0; d < attributeDefinitions.length; d++) + { + const definition = attributeDefinitions[d]; + + if (typeof definition !== 'string') + { + vertexSize += attributeDefinitions[d].size; + } + else + { + if (!builtinAttributeSizes[definition]) + { + throw new Error(`${definition} is not a builtin attribute!`); + } + + vertexSize += builtinAttributeSizes[definition]; + } + } + + return vertexSize; + } } diff --git a/packages/core/src/batch/BatchGeometry.js b/packages/core/src/batch/BatchGeometry.js index 6275f8c..5b8afc4 100644 --- a/packages/core/src/batch/BatchGeometry.js +++ b/packages/core/src/batch/BatchGeometry.js @@ -3,7 +3,8 @@ import Buffer from '../geometry/Buffer'; /** - * Geometry used to batch standard PIXI content (e.g. Mesh, Sprite, Graphics objects). + * Geometry used to batch standard PIXI content (e.g. Mesh, Sprite, + * Graphics objects). * * @class * @memberof PIXI @@ -34,6 +35,7 @@ */ this._indexBuffer = new Buffer(null, _static, true); + /* These are automatically interleaved by GeometrySystem. */ this.addAttribute('aVertexPosition', this._buffer, 2, false, TYPES.FLOAT) .addAttribute('aTextureCoord', this._buffer, 2, false, TYPES.FLOAT) .addAttribute('aColor', this._buffer, 4, true, TYPES.UNSIGNED_BYTE) diff --git a/packages/core/src/batch/BatchPluginFactory.js b/packages/core/src/batch/BatchPluginFactory.js deleted file mode 100644 index bf30ce5..0000000 --- a/packages/core/src/batch/BatchPluginFactory.js +++ /dev/null @@ -1,93 +0,0 @@ -import BatchShaderGenerator from './BatchShaderGenerator'; -import BatchGeometry from './BatchGeometry'; -import AbstractBatchRenderer from './AbstractBatchRenderer'; - -import defaultVertex from './texture.vert'; -import defaultFragment from './texture.frag'; - -/** - * @class - * @memberof PIXI - * @hideconstructor - */ -export default class BatchPluginFactory -{ - /** - * Create a new BatchRenderer plugin for Renderer. this convenience can provide an easy way - * to extend BatchRenderer with all the necessary pieces. - * @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'; - * - * @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 AbstractBatchRenderer - { - 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/AbstractBatchRenderer.js b/packages/core/src/batch/AbstractBatchRenderer.js index 340a382..72a5457 100644 --- a/packages/core/src/batch/AbstractBatchRenderer.js +++ b/packages/core/src/batch/AbstractBatchRenderer.js @@ -9,6 +9,18 @@ import { ENV } from '@pixi/constants'; /** + * Sizes of built-in attributes provided by `BatchRenderer`, + * which includes the required `aTextureId` attribute. + * + * @type Map + * @see {AbstractBatchRenderer#vertexSizeOf} + */ +const builtinAttributeSizes = { + aColor: 1, + aTextureId: 1, +}; + +/** * Renderer dedicated to drawing and batching sprites. * * This is the default batch renderer. It buffers objects @@ -60,15 +72,57 @@ this.geometryClass = null; /** + * Array of attribute definitions that are described + * below. These are used to pass attribute data + * from your objects to the vertex shader. + * + * An attribute definition is an object with the + * following properties: + * + * | Property | Description | + * |------------|-----------------------------------------------| + * | property | Property name in objects for attribute data | + * | identifier | Name of attribute variable used in the shader | + * | size | Size of each attribute element in floats | + * + * `BatchRenderer` provides a few built-in attributes + * that you can use like `aColor`. Instead of the + * attribute definition, just give the identifier to + * use them. + * + * The `attribute float aTextureId` attribute should + * _always_ be present in your shader. See + * `PIXI.BatchShaderGenerator` for more details. + * + * NOTE: It is expected that the attribute buffers + * store an equal number of elements. For example, + * `object.vertexData.length = object.uvs.length`. Otherwise, + * wrapping will occur so that the attribute data is repeated + * for each vertex. + * + * @default + * | Index | AttributeDefinition | + * |-------|--------------------------------------------------------------------| + * | 0 | `{ property: vertexData, identifier: 'aVertexPosition', size: 2 }` | + * | 1 | `{ property: uvs, identifier: 'aTextureCoord', size: 2 }` | + * | 2 | `'aColor'` | + * | 3 | `'aTextureId'` // mandatory | + * + * @readonly + */ + this.attributeDefinitions = null; + + /** * Size of data being buffered per vertex in the * attribute buffers (in floats). By default, the * batch-renderer plugin uses 6: * - * | aVertexPosition | 2 | - * |-----------------|---| - * | aTextureCoords | 2 | - * | aColor | 1 | - * | aTextureId | 1 | + * | Attribute | Size | + * |-----------------|------| + * | aVertexPosition | 2 | + * | aTextureCoords | 2 | + * | aColor | 1 | + * | aTextureId | 1 | * * @member {number} vertexSize * @readonly @@ -593,12 +647,10 @@ } /** - * Takes the four batching parameters of `element`, interleaves - * and pushes them into the batching attribute/index buffers given. - * - * It uses these properties: `vertexData` `uvs`, `textureId` and - * `indicies`. It also uses the "tint" of the base-texture, if - * present. + * Takes all the attributes sources of the element being + * drawn, interleaves them, and appends them to the + * attribute buffer. It also appends the indices of the + * element into the index buffer. * * @param {PIXI.Sprite} element - element being rendered * @param {Float32Array} float32View - float32-view of the attribute buffer @@ -610,32 +662,119 @@ packInterleavedGeometry(element, float32View, uint32View, indexBuffer, aIndex, iIndex) { - const p = aIndex / this.vertexSize; - const uvs = element.uvs; + const packedVerticies = aIndex / this.vertexSize; const indicies = element.indices; - const vertexData = element.vertexData; const textureId = element._texture.baseTexture._id; - const alpha = Math.min(element.worldAlpha, 1.0); - const argb = (alpha < 1.0 - && element._texture.baseTexture.premultiplyAlpha) - ? premultiplyTint(element._tintRGB, alpha) - : element._tintRGB + (alpha * 255 << 24); + const attributeDefinitions = this.attributeDefinitions; + const attributeSources = []; + let highestAttributeLength = 0; - // lets not worry about tint! for now.. - for (let i = 0; i < vertexData.length; i += 2) + for (let i = 0; i < attributeDefinitions.length; i++) { - float32View[aIndex++] = vertexData[i]; - float32View[aIndex++] = vertexData[i + 1]; - float32View[aIndex++] = uvs[i]; - float32View[aIndex++] = uvs[i + 1]; - uint32View[aIndex++] = argb; - float32View[aIndex++] = textureId; + const attribute = attributeDefinitions[i]; + + if (typeof attributeDefinitions[i] !== 'string') + { + const source = element[attributeDefinitions[i].property]; + + attributeSources.push(source); + highestAttributeLength = Math.max( + highestAttributeLength, source.length / attribute.size); + } + else + { + switch (attributeDefinitions[i]) + { + case 'aColor': + { + const alpha = Math.min(element.worldAlpha, 1.0); + const argb = (alpha < 1.0 + && element._texture.baseTexture.premultiplyAlpha) + ? premultiplyTint(element._tintRGB, alpha) + : element._tintRGB + (alpha * 255 << 24); + + attributeSources.push([Math.round(argb)]); + highestAttributeLength + = Math.max(highestAttributeLength, 1); + break; + } + case 'aTextureId': + { + attributeSources.push(null); + break; + } + default: + { + throw new Error(`Unknown built-in attribute ` + + `given to AbstractBatchRenderer: ` + + `${attributeDefinitions[i]}`); + } + } + } + } + + for (let d = 0; d < attributeDefinitions.length; d++) + { + attributeDefinitions[d].offset = 0; + } + + for (let i = 0; i < highestAttributeLength; i++) + { + for (let s = 0; s < attributeSources.length; s++) + { + const attribute = attributeDefinitions[s]; + const source = attributeSources[s]; + + if (!source)// Only aTextureId has no source! + { + float32View[aIndex++] = textureId; + continue; + } + + for (let float = 0; float < attribute.size; float++) + { + float32View[aIndex++] = source[attribute.offset++ % source.length]; + } + } } for (let i = 0; i < indicies.length; i++) { - indexBuffer[iIndex++] = p + indicies[i]; + indexBuffer[iIndex++] = packedVerticies + indicies[i]; } } + + /** + * Calculates the vertex size for the given attribute + * definitions. It also accounts for built-in attributes. + * + * @param {Array} attributeDefinitions - attribute definitions + * @return sum of all attribute sizes + */ + static vertexSizeOf(attributeDefinitions) + { + let vertexSize = 0; + + for (let d = 0; d < attributeDefinitions.length; d++) + { + const definition = attributeDefinitions[d]; + + if (typeof definition !== 'string') + { + vertexSize += attributeDefinitions[d].size; + } + else + { + if (!builtinAttributeSizes[definition]) + { + throw new Error(`${definition} is not a builtin attribute!`); + } + + vertexSize += builtinAttributeSizes[definition]; + } + } + + return vertexSize; + } } diff --git a/packages/core/src/batch/BatchGeometry.js b/packages/core/src/batch/BatchGeometry.js index 6275f8c..5b8afc4 100644 --- a/packages/core/src/batch/BatchGeometry.js +++ b/packages/core/src/batch/BatchGeometry.js @@ -3,7 +3,8 @@ import Buffer from '../geometry/Buffer'; /** - * Geometry used to batch standard PIXI content (e.g. Mesh, Sprite, Graphics objects). + * Geometry used to batch standard PIXI content (e.g. Mesh, Sprite, + * Graphics objects). * * @class * @memberof PIXI @@ -34,6 +35,7 @@ */ this._indexBuffer = new Buffer(null, _static, true); + /* These are automatically interleaved by GeometrySystem. */ this.addAttribute('aVertexPosition', this._buffer, 2, false, TYPES.FLOAT) .addAttribute('aTextureCoord', this._buffer, 2, false, TYPES.FLOAT) .addAttribute('aColor', this._buffer, 4, true, TYPES.UNSIGNED_BYTE) diff --git a/packages/core/src/batch/BatchPluginFactory.js b/packages/core/src/batch/BatchPluginFactory.js deleted file mode 100644 index bf30ce5..0000000 --- a/packages/core/src/batch/BatchPluginFactory.js +++ /dev/null @@ -1,93 +0,0 @@ -import BatchShaderGenerator from './BatchShaderGenerator'; -import BatchGeometry from './BatchGeometry'; -import AbstractBatchRenderer from './AbstractBatchRenderer'; - -import defaultVertex from './texture.vert'; -import defaultFragment from './texture.frag'; - -/** - * @class - * @memberof PIXI - * @hideconstructor - */ -export default class BatchPluginFactory -{ - /** - * Create a new BatchRenderer plugin for Renderer. this convenience can provide an easy way - * to extend BatchRenderer with all the necessary pieces. - * @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'; - * - * @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 AbstractBatchRenderer - { - 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/BatchRendererFactory.js b/packages/core/src/batch/BatchRendererFactory.js new file mode 100644 index 0000000..f944361 --- /dev/null +++ b/packages/core/src/batch/BatchRendererFactory.js @@ -0,0 +1,106 @@ +import BatchShaderGenerator from './BatchShaderGenerator'; +import BatchGeometry from './BatchGeometry'; +import AbstractBatchRenderer from './AbstractBatchRenderer'; + +import defaultVertex from './texture.vert'; +import defaultFragment from './texture.frag'; + +/** + * @class + * @memberof PIXI + * @hideconstructor + */ +export default class BatchRendererFactory +{ + /** + * Create a new BatchRenderer plugin for Renderer. this convenience can provide an easy way + * to extend BatchRenderer with all the necessary pieces. + * @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'; + * + * @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 { + attributeDefinitions, + fragment, + geometryClass, + vertex, + } = Object.assign({ + attributeDefinitions: [ + { property: 'vertexData', name: 'aTexturePosition', size: 2 }, + { property: 'uvs', name: 'aTextureCoord', size: 2 }, + 'aColor', // built-in attribute + 'aTextureId', + ], + vertex: defaultVertex, + fragment: defaultFragment, + geometryClass: BatchGeometry, + }, options); + + const vertexSize = AbstractBatchRenderer.vertexSizeOf(attributeDefinitions); + + return class BatchPlugin extends AbstractBatchRenderer + { + constructor(renderer) + { + super(renderer); + + this.attributeDefinitions = attributeDefinitions; + 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 = BatchRendererFactory.create(); diff --git a/packages/core/src/batch/AbstractBatchRenderer.js b/packages/core/src/batch/AbstractBatchRenderer.js index 340a382..72a5457 100644 --- a/packages/core/src/batch/AbstractBatchRenderer.js +++ b/packages/core/src/batch/AbstractBatchRenderer.js @@ -9,6 +9,18 @@ import { ENV } from '@pixi/constants'; /** + * Sizes of built-in attributes provided by `BatchRenderer`, + * which includes the required `aTextureId` attribute. + * + * @type Map + * @see {AbstractBatchRenderer#vertexSizeOf} + */ +const builtinAttributeSizes = { + aColor: 1, + aTextureId: 1, +}; + +/** * Renderer dedicated to drawing and batching sprites. * * This is the default batch renderer. It buffers objects @@ -60,15 +72,57 @@ this.geometryClass = null; /** + * Array of attribute definitions that are described + * below. These are used to pass attribute data + * from your objects to the vertex shader. + * + * An attribute definition is an object with the + * following properties: + * + * | Property | Description | + * |------------|-----------------------------------------------| + * | property | Property name in objects for attribute data | + * | identifier | Name of attribute variable used in the shader | + * | size | Size of each attribute element in floats | + * + * `BatchRenderer` provides a few built-in attributes + * that you can use like `aColor`. Instead of the + * attribute definition, just give the identifier to + * use them. + * + * The `attribute float aTextureId` attribute should + * _always_ be present in your shader. See + * `PIXI.BatchShaderGenerator` for more details. + * + * NOTE: It is expected that the attribute buffers + * store an equal number of elements. For example, + * `object.vertexData.length = object.uvs.length`. Otherwise, + * wrapping will occur so that the attribute data is repeated + * for each vertex. + * + * @default + * | Index | AttributeDefinition | + * |-------|--------------------------------------------------------------------| + * | 0 | `{ property: vertexData, identifier: 'aVertexPosition', size: 2 }` | + * | 1 | `{ property: uvs, identifier: 'aTextureCoord', size: 2 }` | + * | 2 | `'aColor'` | + * | 3 | `'aTextureId'` // mandatory | + * + * @readonly + */ + this.attributeDefinitions = null; + + /** * Size of data being buffered per vertex in the * attribute buffers (in floats). By default, the * batch-renderer plugin uses 6: * - * | aVertexPosition | 2 | - * |-----------------|---| - * | aTextureCoords | 2 | - * | aColor | 1 | - * | aTextureId | 1 | + * | Attribute | Size | + * |-----------------|------| + * | aVertexPosition | 2 | + * | aTextureCoords | 2 | + * | aColor | 1 | + * | aTextureId | 1 | * * @member {number} vertexSize * @readonly @@ -593,12 +647,10 @@ } /** - * Takes the four batching parameters of `element`, interleaves - * and pushes them into the batching attribute/index buffers given. - * - * It uses these properties: `vertexData` `uvs`, `textureId` and - * `indicies`. It also uses the "tint" of the base-texture, if - * present. + * Takes all the attributes sources of the element being + * drawn, interleaves them, and appends them to the + * attribute buffer. It also appends the indices of the + * element into the index buffer. * * @param {PIXI.Sprite} element - element being rendered * @param {Float32Array} float32View - float32-view of the attribute buffer @@ -610,32 +662,119 @@ packInterleavedGeometry(element, float32View, uint32View, indexBuffer, aIndex, iIndex) { - const p = aIndex / this.vertexSize; - const uvs = element.uvs; + const packedVerticies = aIndex / this.vertexSize; const indicies = element.indices; - const vertexData = element.vertexData; const textureId = element._texture.baseTexture._id; - const alpha = Math.min(element.worldAlpha, 1.0); - const argb = (alpha < 1.0 - && element._texture.baseTexture.premultiplyAlpha) - ? premultiplyTint(element._tintRGB, alpha) - : element._tintRGB + (alpha * 255 << 24); + const attributeDefinitions = this.attributeDefinitions; + const attributeSources = []; + let highestAttributeLength = 0; - // lets not worry about tint! for now.. - for (let i = 0; i < vertexData.length; i += 2) + for (let i = 0; i < attributeDefinitions.length; i++) { - float32View[aIndex++] = vertexData[i]; - float32View[aIndex++] = vertexData[i + 1]; - float32View[aIndex++] = uvs[i]; - float32View[aIndex++] = uvs[i + 1]; - uint32View[aIndex++] = argb; - float32View[aIndex++] = textureId; + const attribute = attributeDefinitions[i]; + + if (typeof attributeDefinitions[i] !== 'string') + { + const source = element[attributeDefinitions[i].property]; + + attributeSources.push(source); + highestAttributeLength = Math.max( + highestAttributeLength, source.length / attribute.size); + } + else + { + switch (attributeDefinitions[i]) + { + case 'aColor': + { + const alpha = Math.min(element.worldAlpha, 1.0); + const argb = (alpha < 1.0 + && element._texture.baseTexture.premultiplyAlpha) + ? premultiplyTint(element._tintRGB, alpha) + : element._tintRGB + (alpha * 255 << 24); + + attributeSources.push([Math.round(argb)]); + highestAttributeLength + = Math.max(highestAttributeLength, 1); + break; + } + case 'aTextureId': + { + attributeSources.push(null); + break; + } + default: + { + throw new Error(`Unknown built-in attribute ` + + `given to AbstractBatchRenderer: ` + + `${attributeDefinitions[i]}`); + } + } + } + } + + for (let d = 0; d < attributeDefinitions.length; d++) + { + attributeDefinitions[d].offset = 0; + } + + for (let i = 0; i < highestAttributeLength; i++) + { + for (let s = 0; s < attributeSources.length; s++) + { + const attribute = attributeDefinitions[s]; + const source = attributeSources[s]; + + if (!source)// Only aTextureId has no source! + { + float32View[aIndex++] = textureId; + continue; + } + + for (let float = 0; float < attribute.size; float++) + { + float32View[aIndex++] = source[attribute.offset++ % source.length]; + } + } } for (let i = 0; i < indicies.length; i++) { - indexBuffer[iIndex++] = p + indicies[i]; + indexBuffer[iIndex++] = packedVerticies + indicies[i]; } } + + /** + * Calculates the vertex size for the given attribute + * definitions. It also accounts for built-in attributes. + * + * @param {Array} attributeDefinitions - attribute definitions + * @return sum of all attribute sizes + */ + static vertexSizeOf(attributeDefinitions) + { + let vertexSize = 0; + + for (let d = 0; d < attributeDefinitions.length; d++) + { + const definition = attributeDefinitions[d]; + + if (typeof definition !== 'string') + { + vertexSize += attributeDefinitions[d].size; + } + else + { + if (!builtinAttributeSizes[definition]) + { + throw new Error(`${definition} is not a builtin attribute!`); + } + + vertexSize += builtinAttributeSizes[definition]; + } + } + + return vertexSize; + } } diff --git a/packages/core/src/batch/BatchGeometry.js b/packages/core/src/batch/BatchGeometry.js index 6275f8c..5b8afc4 100644 --- a/packages/core/src/batch/BatchGeometry.js +++ b/packages/core/src/batch/BatchGeometry.js @@ -3,7 +3,8 @@ import Buffer from '../geometry/Buffer'; /** - * Geometry used to batch standard PIXI content (e.g. Mesh, Sprite, Graphics objects). + * Geometry used to batch standard PIXI content (e.g. Mesh, Sprite, + * Graphics objects). * * @class * @memberof PIXI @@ -34,6 +35,7 @@ */ this._indexBuffer = new Buffer(null, _static, true); + /* These are automatically interleaved by GeometrySystem. */ this.addAttribute('aVertexPosition', this._buffer, 2, false, TYPES.FLOAT) .addAttribute('aTextureCoord', this._buffer, 2, false, TYPES.FLOAT) .addAttribute('aColor', this._buffer, 4, true, TYPES.UNSIGNED_BYTE) diff --git a/packages/core/src/batch/BatchPluginFactory.js b/packages/core/src/batch/BatchPluginFactory.js deleted file mode 100644 index bf30ce5..0000000 --- a/packages/core/src/batch/BatchPluginFactory.js +++ /dev/null @@ -1,93 +0,0 @@ -import BatchShaderGenerator from './BatchShaderGenerator'; -import BatchGeometry from './BatchGeometry'; -import AbstractBatchRenderer from './AbstractBatchRenderer'; - -import defaultVertex from './texture.vert'; -import defaultFragment from './texture.frag'; - -/** - * @class - * @memberof PIXI - * @hideconstructor - */ -export default class BatchPluginFactory -{ - /** - * Create a new BatchRenderer plugin for Renderer. this convenience can provide an easy way - * to extend BatchRenderer with all the necessary pieces. - * @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'; - * - * @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 AbstractBatchRenderer - { - 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/BatchRendererFactory.js b/packages/core/src/batch/BatchRendererFactory.js new file mode 100644 index 0000000..f944361 --- /dev/null +++ b/packages/core/src/batch/BatchRendererFactory.js @@ -0,0 +1,106 @@ +import BatchShaderGenerator from './BatchShaderGenerator'; +import BatchGeometry from './BatchGeometry'; +import AbstractBatchRenderer from './AbstractBatchRenderer'; + +import defaultVertex from './texture.vert'; +import defaultFragment from './texture.frag'; + +/** + * @class + * @memberof PIXI + * @hideconstructor + */ +export default class BatchRendererFactory +{ + /** + * Create a new BatchRenderer plugin for Renderer. this convenience can provide an easy way + * to extend BatchRenderer with all the necessary pieces. + * @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'; + * + * @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 { + attributeDefinitions, + fragment, + geometryClass, + vertex, + } = Object.assign({ + attributeDefinitions: [ + { property: 'vertexData', name: 'aTexturePosition', size: 2 }, + { property: 'uvs', name: 'aTextureCoord', size: 2 }, + 'aColor', // built-in attribute + 'aTextureId', + ], + vertex: defaultVertex, + fragment: defaultFragment, + geometryClass: BatchGeometry, + }, options); + + const vertexSize = AbstractBatchRenderer.vertexSizeOf(attributeDefinitions); + + return class BatchPlugin extends AbstractBatchRenderer + { + constructor(renderer) + { + super(renderer); + + this.attributeDefinitions = attributeDefinitions; + 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 = BatchRendererFactory.create(); diff --git a/packages/core/src/index.js b/packages/core/src/index.js index d4cb1fa..a778ec4 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -22,7 +22,7 @@ export { default as TextureUvs } from './textures/TextureUvs'; export { default as State } from './state/State'; export { default as ObjectRenderer } from './batch/ObjectRenderer'; -export { default as BatchPluginFactory, BatchRenderer } from './batch/BatchPluginFactory'; +export { default as BatchRendererFactory, BatchRenderer } from './batch/BatchRendererFactory'; export { default as BatchShaderGenerator } from './batch/BatchShaderGenerator'; export { default as BatchGeometry } from './batch/BatchGeometry'; export { default as BatchDrawCall } from './batch/BatchDrawCall';