diff --git a/packages/constants/src/index.js b/packages/constants/src/index.js index 64a3a0a..c92ea55 100644 --- a/packages/constants/src/index.js +++ b/packages/constants/src/index.js @@ -212,6 +212,43 @@ UNSIGNED_SHORT_5_5_5_1: 32820, FLOAT: 5126, HALF_FLOAT: 36193, + + /** + * Returns if any type in the `TYPES` enum is of integer + * nature. + * + * @param {number} glint - `TYPES` types + * @return if `glint` is an integral type + */ + isInteger(glint) + { + return (glint !== TYPES.FLOAT) && (glint !== TYPES.HALF_FLOAT); + }, + + /** + * Returns the size of any type in the `TYPES` enum. + * + * @param {number} glint - TYPES enum constant + * @return size of `glint` in bytes + */ + sizeOf(glint) + { + switch (glint) + { + case TYPES.FLOAT: + return 4; + case TYPES.HALF_FLOAT: + case TYPES.UNSIGNED_SHORT_5_5_5_1: + case TYPES.UNSIGNED_SHORT_4_4_4_4: + case TYPES.UNSIGNED_SHORT_5_6_5: + case TYPES.UNSIGNED_SHORT: + return 2; + case TYPES.UNSIGNED_BYTE: + return 1; + default: + throw new Error(`{$glint} isn't a TYPES enum!`); + } + }, }; /** diff --git a/packages/constants/src/index.js b/packages/constants/src/index.js index 64a3a0a..c92ea55 100644 --- a/packages/constants/src/index.js +++ b/packages/constants/src/index.js @@ -212,6 +212,43 @@ UNSIGNED_SHORT_5_5_5_1: 32820, FLOAT: 5126, HALF_FLOAT: 36193, + + /** + * Returns if any type in the `TYPES` enum is of integer + * nature. + * + * @param {number} glint - `TYPES` types + * @return if `glint` is an integral type + */ + isInteger(glint) + { + return (glint !== TYPES.FLOAT) && (glint !== TYPES.HALF_FLOAT); + }, + + /** + * Returns the size of any type in the `TYPES` enum. + * + * @param {number} glint - TYPES enum constant + * @return size of `glint` in bytes + */ + sizeOf(glint) + { + switch (glint) + { + case TYPES.FLOAT: + return 4; + case TYPES.HALF_FLOAT: + case TYPES.UNSIGNED_SHORT_5_5_5_1: + case TYPES.UNSIGNED_SHORT_4_4_4_4: + case TYPES.UNSIGNED_SHORT_5_6_5: + case TYPES.UNSIGNED_SHORT: + return 2; + case TYPES.UNSIGNED_BYTE: + return 1; + default: + throw new Error(`{$glint} isn't a TYPES enum!`); + } + }, }; /** diff --git a/packages/core/src/batch/AbstractBatchRenderer.js b/packages/core/src/batch/AbstractBatchRenderer.js index 8cbefe0..0c41545 100644 --- a/packages/core/src/batch/AbstractBatchRenderer.js +++ b/packages/core/src/batch/AbstractBatchRenderer.js @@ -1,3 +1,4 @@ +import builtinAttributeDefinitions from './utils/builtinAttributeDefinitions'; import BatchDrawCall from './BatchDrawCall'; import BaseTexture from '../textures/BaseTexture'; import builtinAttributeSizes from './utils/builtinAttributeSizes'; @@ -6,7 +7,7 @@ import checkMaxIfStatementsInShader from '../shader/utils/checkMaxIfStatementsInShader'; import { settings } from '@pixi/settings'; import { premultiplyBlendMode, premultiplyTint, nextPow2, log2 } from '@pixi/utils'; -import BatchBuffer from './BatchBuffer'; +import ViewableBuffer from '../geometry/ViewableBuffer'; import { ENV } from '@pixi/constants'; /** @@ -90,12 +91,12 @@ * for each vertex. * * @default - * | Index | AttributeDefinition | - * |-------|--------------------------------------------------------------| - * | 0 | `{ property: vertexData, name: 'aVertexPosition', size: 2 }` | - * | 1 | `{ property: uvs, name: 'aTextureCoord', size: 2 }` | - * | 2 | `'aColor'` | - * | 3 | `'aTextureId'` // mandatory | + * | Index | property | name | type | size | glType | glSize | + * |-------|------------|-----------------|-----------|------|----------------------|--------| + * | 1 | vertexData | aVertexPosition | `float32` | 2 | TYPES.FLOAT | 1 | + * | 2 | uvs | aTextureCoord | `float32` | 2 | TYPES.FLOAT | 1 | + * | 3 | undefined | aColor | `uint32` | 1 | TYPES.UNSIGNED_BYTE | 4 | + * | 4 | undefined | aTextureId | `float32` | 1 | TYPES.FLOAT | 1 | * * @readonly */ @@ -238,7 +239,7 @@ } /** - * Pool of `BatchBuffer` objects that are sorted in + * Pool of `ViewableBuffer` objects that are sorted in * order of increasing size. The flush method uses * the buffer with the least size above the amount * it requires. These are used for passing attributes. @@ -246,7 +247,7 @@ * The first buffer has a size of 8; each subsequent * buffer has double capacity of its previous. * - * @member {PIXI.BatchBuffer} + * @member {PIXI.ViewableBuffer} * @private * @see PIXI.BatchRenderer#getAttributeBuffer */ @@ -379,10 +380,6 @@ vertexSize, } = this; - const { - float32View, uint32View, - } = attrBuffer; - const touch = this.renderer.textureGC.count; let attrIndex = 0; let iIndex = 0; @@ -451,7 +448,7 @@ } } - this.packInterleavedGeometry(sprite, float32View, uint32View, + this.packInterleavedGeometry(sprite, attrBuffer, indexBuffer, attrIndex, iIndex); // push a graphics.. @@ -580,7 +577,7 @@ * can hold atleast `size` floats. * * @param {number} size - minimum capacity required - * @return {BatchBuffer} - buffer than can hold atleast `size` floats + * @return {ViewableBuffer} - buffer than can hold atleast `size` floats * @private */ getAttributeBuffer(size) @@ -599,7 +596,7 @@ if (!buffer) { - this._aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertexSize * 4); + this._aBuffers[roundedSize] = buffer = new ViewableBuffer(roundedSize * this.vertexSize * 4); } return buffer; @@ -643,14 +640,12 @@ * element into the index buffer. * * @param {PIXI.Sprite} element - element being rendered - * @param {Float32Array} float32View - float32-view of the attribute buffer - * @param {Uint32Array} uint32View - uint32-view of the attribute buffer + * @param {PIXI.ViewableBuffer} attributeBuffer - attribute buffer * @param {Uint16Array} indexBuffer - index buffer * @param {number} aIndex - number of floats already in the attribute buffer * @param {number} iIndex - number of indices already in `indexBuffer` */ - packInterleavedGeometry(element, - float32View, uint32View, indexBuffer, aIndex, iIndex) + packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) { const packedVerticies = aIndex / this.vertexSize; const indicies = element.indices; @@ -715,23 +710,29 @@ if (!source)// Only aTextureId has no source! { - float32View[aIndex++] = textureId; + attributeBuffer.float32View[aIndex++] = textureId; continue; } - let offset = sourceOffsets[s]; - const size = (typeof attribute !== 'string') - ? attribute.size : builtinAttributeSizes[attribute]; + const isBuiltin = (typeof attribute === 'string'); + const type = (isBuiltin) ? builtinAttributeDefinitions[attribute].type + : attribute.type; + const size = (isBuiltin) ? builtinAttributeDefinitions[attribute].size + : attribute.size; + const wordSize = (isBuiltin) ? builtinAttributeDefinitions[attribute]._wordSize + : attribute._wordSize;// size of each attribute in words + const typeWordSize = wordSize / size;// size of type in words - for (let float = 0; float < size; float++) + let offset = sourceOffsets[s]; + let globalOffset = aIndex / typeWordSize; + + for (let localOffset = 0; localOffset < size; localOffset++) { - if (attribute === 'aColor') - { uint32View[aIndex++] = source[offset++ % source.length]; } - else - { float32View[aIndex++] = source[offset++ % source.length]; } + attributeBuffer.view(type)[globalOffset++] = source[offset++ % source.length]; } sourceOffsets[s] = offset; + aIndex = globalOffset * typeWordSize; } } diff --git a/packages/constants/src/index.js b/packages/constants/src/index.js index 64a3a0a..c92ea55 100644 --- a/packages/constants/src/index.js +++ b/packages/constants/src/index.js @@ -212,6 +212,43 @@ UNSIGNED_SHORT_5_5_5_1: 32820, FLOAT: 5126, HALF_FLOAT: 36193, + + /** + * Returns if any type in the `TYPES` enum is of integer + * nature. + * + * @param {number} glint - `TYPES` types + * @return if `glint` is an integral type + */ + isInteger(glint) + { + return (glint !== TYPES.FLOAT) && (glint !== TYPES.HALF_FLOAT); + }, + + /** + * Returns the size of any type in the `TYPES` enum. + * + * @param {number} glint - TYPES enum constant + * @return size of `glint` in bytes + */ + sizeOf(glint) + { + switch (glint) + { + case TYPES.FLOAT: + return 4; + case TYPES.HALF_FLOAT: + case TYPES.UNSIGNED_SHORT_5_5_5_1: + case TYPES.UNSIGNED_SHORT_4_4_4_4: + case TYPES.UNSIGNED_SHORT_5_6_5: + case TYPES.UNSIGNED_SHORT: + return 2; + case TYPES.UNSIGNED_BYTE: + return 1; + default: + throw new Error(`{$glint} isn't a TYPES enum!`); + } + }, }; /** diff --git a/packages/core/src/batch/AbstractBatchRenderer.js b/packages/core/src/batch/AbstractBatchRenderer.js index 8cbefe0..0c41545 100644 --- a/packages/core/src/batch/AbstractBatchRenderer.js +++ b/packages/core/src/batch/AbstractBatchRenderer.js @@ -1,3 +1,4 @@ +import builtinAttributeDefinitions from './utils/builtinAttributeDefinitions'; import BatchDrawCall from './BatchDrawCall'; import BaseTexture from '../textures/BaseTexture'; import builtinAttributeSizes from './utils/builtinAttributeSizes'; @@ -6,7 +7,7 @@ import checkMaxIfStatementsInShader from '../shader/utils/checkMaxIfStatementsInShader'; import { settings } from '@pixi/settings'; import { premultiplyBlendMode, premultiplyTint, nextPow2, log2 } from '@pixi/utils'; -import BatchBuffer from './BatchBuffer'; +import ViewableBuffer from '../geometry/ViewableBuffer'; import { ENV } from '@pixi/constants'; /** @@ -90,12 +91,12 @@ * for each vertex. * * @default - * | Index | AttributeDefinition | - * |-------|--------------------------------------------------------------| - * | 0 | `{ property: vertexData, name: 'aVertexPosition', size: 2 }` | - * | 1 | `{ property: uvs, name: 'aTextureCoord', size: 2 }` | - * | 2 | `'aColor'` | - * | 3 | `'aTextureId'` // mandatory | + * | Index | property | name | type | size | glType | glSize | + * |-------|------------|-----------------|-----------|------|----------------------|--------| + * | 1 | vertexData | aVertexPosition | `float32` | 2 | TYPES.FLOAT | 1 | + * | 2 | uvs | aTextureCoord | `float32` | 2 | TYPES.FLOAT | 1 | + * | 3 | undefined | aColor | `uint32` | 1 | TYPES.UNSIGNED_BYTE | 4 | + * | 4 | undefined | aTextureId | `float32` | 1 | TYPES.FLOAT | 1 | * * @readonly */ @@ -238,7 +239,7 @@ } /** - * Pool of `BatchBuffer` objects that are sorted in + * Pool of `ViewableBuffer` objects that are sorted in * order of increasing size. The flush method uses * the buffer with the least size above the amount * it requires. These are used for passing attributes. @@ -246,7 +247,7 @@ * The first buffer has a size of 8; each subsequent * buffer has double capacity of its previous. * - * @member {PIXI.BatchBuffer} + * @member {PIXI.ViewableBuffer} * @private * @see PIXI.BatchRenderer#getAttributeBuffer */ @@ -379,10 +380,6 @@ vertexSize, } = this; - const { - float32View, uint32View, - } = attrBuffer; - const touch = this.renderer.textureGC.count; let attrIndex = 0; let iIndex = 0; @@ -451,7 +448,7 @@ } } - this.packInterleavedGeometry(sprite, float32View, uint32View, + this.packInterleavedGeometry(sprite, attrBuffer, indexBuffer, attrIndex, iIndex); // push a graphics.. @@ -580,7 +577,7 @@ * can hold atleast `size` floats. * * @param {number} size - minimum capacity required - * @return {BatchBuffer} - buffer than can hold atleast `size` floats + * @return {ViewableBuffer} - buffer than can hold atleast `size` floats * @private */ getAttributeBuffer(size) @@ -599,7 +596,7 @@ if (!buffer) { - this._aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertexSize * 4); + this._aBuffers[roundedSize] = buffer = new ViewableBuffer(roundedSize * this.vertexSize * 4); } return buffer; @@ -643,14 +640,12 @@ * element into the index buffer. * * @param {PIXI.Sprite} element - element being rendered - * @param {Float32Array} float32View - float32-view of the attribute buffer - * @param {Uint32Array} uint32View - uint32-view of the attribute buffer + * @param {PIXI.ViewableBuffer} attributeBuffer - attribute buffer * @param {Uint16Array} indexBuffer - index buffer * @param {number} aIndex - number of floats already in the attribute buffer * @param {number} iIndex - number of indices already in `indexBuffer` */ - packInterleavedGeometry(element, - float32View, uint32View, indexBuffer, aIndex, iIndex) + packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) { const packedVerticies = aIndex / this.vertexSize; const indicies = element.indices; @@ -715,23 +710,29 @@ if (!source)// Only aTextureId has no source! { - float32View[aIndex++] = textureId; + attributeBuffer.float32View[aIndex++] = textureId; continue; } - let offset = sourceOffsets[s]; - const size = (typeof attribute !== 'string') - ? attribute.size : builtinAttributeSizes[attribute]; + const isBuiltin = (typeof attribute === 'string'); + const type = (isBuiltin) ? builtinAttributeDefinitions[attribute].type + : attribute.type; + const size = (isBuiltin) ? builtinAttributeDefinitions[attribute].size + : attribute.size; + const wordSize = (isBuiltin) ? builtinAttributeDefinitions[attribute]._wordSize + : attribute._wordSize;// size of each attribute in words + const typeWordSize = wordSize / size;// size of type in words - for (let float = 0; float < size; float++) + let offset = sourceOffsets[s]; + let globalOffset = aIndex / typeWordSize; + + for (let localOffset = 0; localOffset < size; localOffset++) { - if (attribute === 'aColor') - { uint32View[aIndex++] = source[offset++ % source.length]; } - else - { float32View[aIndex++] = source[offset++ % source.length]; } + attributeBuffer.view(type)[globalOffset++] = source[offset++ % source.length]; } sourceOffsets[s] = offset; + aIndex = globalOffset * typeWordSize; } } diff --git a/packages/core/src/batch/BatchBuffer.js b/packages/core/src/batch/BatchBuffer.js deleted file mode 100644 index 5ee8a5f..0000000 --- a/packages/core/src/batch/BatchBuffer.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * `ArrayBuffer` wrapper with float32 and uint32 views. It - * is used by `AbstractBatchRenderer` to store interleaved - * object geometries. - * - * @class - * @memberof PIXI - */ -export default class BatchBuffer -{ - /** - * @param {number} size - The size of the buffer in bytes. - */ - constructor(size) - { - /** - * Underlying `ArrayBuffer` that holds all the data - * and is of capacity `size`. - * - * @member {ArrayBuffer} - */ - this.rawBinaryData = new ArrayBuffer(size); - - /** - * View on the raw binary data as a `Float32Array`. - * - * @member {Float32Array} - */ - this.float32View = new Float32Array(this.rawBinaryData); - - /** - * View on the raw binary data as a `Uint32Array`. - * - * @member {Uint32Array} - */ - this.uint32View = new Uint32Array(this.rawBinaryData); - } - - /** - * Destroys all buffer references. - */ - destroy() - { - this.rawBinaryData = null; - this.float32View = null; - this.uint32View = null; - } -} diff --git a/packages/constants/src/index.js b/packages/constants/src/index.js index 64a3a0a..c92ea55 100644 --- a/packages/constants/src/index.js +++ b/packages/constants/src/index.js @@ -212,6 +212,43 @@ UNSIGNED_SHORT_5_5_5_1: 32820, FLOAT: 5126, HALF_FLOAT: 36193, + + /** + * Returns if any type in the `TYPES` enum is of integer + * nature. + * + * @param {number} glint - `TYPES` types + * @return if `glint` is an integral type + */ + isInteger(glint) + { + return (glint !== TYPES.FLOAT) && (glint !== TYPES.HALF_FLOAT); + }, + + /** + * Returns the size of any type in the `TYPES` enum. + * + * @param {number} glint - TYPES enum constant + * @return size of `glint` in bytes + */ + sizeOf(glint) + { + switch (glint) + { + case TYPES.FLOAT: + return 4; + case TYPES.HALF_FLOAT: + case TYPES.UNSIGNED_SHORT_5_5_5_1: + case TYPES.UNSIGNED_SHORT_4_4_4_4: + case TYPES.UNSIGNED_SHORT_5_6_5: + case TYPES.UNSIGNED_SHORT: + return 2; + case TYPES.UNSIGNED_BYTE: + return 1; + default: + throw new Error(`{$glint} isn't a TYPES enum!`); + } + }, }; /** diff --git a/packages/core/src/batch/AbstractBatchRenderer.js b/packages/core/src/batch/AbstractBatchRenderer.js index 8cbefe0..0c41545 100644 --- a/packages/core/src/batch/AbstractBatchRenderer.js +++ b/packages/core/src/batch/AbstractBatchRenderer.js @@ -1,3 +1,4 @@ +import builtinAttributeDefinitions from './utils/builtinAttributeDefinitions'; import BatchDrawCall from './BatchDrawCall'; import BaseTexture from '../textures/BaseTexture'; import builtinAttributeSizes from './utils/builtinAttributeSizes'; @@ -6,7 +7,7 @@ import checkMaxIfStatementsInShader from '../shader/utils/checkMaxIfStatementsInShader'; import { settings } from '@pixi/settings'; import { premultiplyBlendMode, premultiplyTint, nextPow2, log2 } from '@pixi/utils'; -import BatchBuffer from './BatchBuffer'; +import ViewableBuffer from '../geometry/ViewableBuffer'; import { ENV } from '@pixi/constants'; /** @@ -90,12 +91,12 @@ * for each vertex. * * @default - * | Index | AttributeDefinition | - * |-------|--------------------------------------------------------------| - * | 0 | `{ property: vertexData, name: 'aVertexPosition', size: 2 }` | - * | 1 | `{ property: uvs, name: 'aTextureCoord', size: 2 }` | - * | 2 | `'aColor'` | - * | 3 | `'aTextureId'` // mandatory | + * | Index | property | name | type | size | glType | glSize | + * |-------|------------|-----------------|-----------|------|----------------------|--------| + * | 1 | vertexData | aVertexPosition | `float32` | 2 | TYPES.FLOAT | 1 | + * | 2 | uvs | aTextureCoord | `float32` | 2 | TYPES.FLOAT | 1 | + * | 3 | undefined | aColor | `uint32` | 1 | TYPES.UNSIGNED_BYTE | 4 | + * | 4 | undefined | aTextureId | `float32` | 1 | TYPES.FLOAT | 1 | * * @readonly */ @@ -238,7 +239,7 @@ } /** - * Pool of `BatchBuffer` objects that are sorted in + * Pool of `ViewableBuffer` objects that are sorted in * order of increasing size. The flush method uses * the buffer with the least size above the amount * it requires. These are used for passing attributes. @@ -246,7 +247,7 @@ * The first buffer has a size of 8; each subsequent * buffer has double capacity of its previous. * - * @member {PIXI.BatchBuffer} + * @member {PIXI.ViewableBuffer} * @private * @see PIXI.BatchRenderer#getAttributeBuffer */ @@ -379,10 +380,6 @@ vertexSize, } = this; - const { - float32View, uint32View, - } = attrBuffer; - const touch = this.renderer.textureGC.count; let attrIndex = 0; let iIndex = 0; @@ -451,7 +448,7 @@ } } - this.packInterleavedGeometry(sprite, float32View, uint32View, + this.packInterleavedGeometry(sprite, attrBuffer, indexBuffer, attrIndex, iIndex); // push a graphics.. @@ -580,7 +577,7 @@ * can hold atleast `size` floats. * * @param {number} size - minimum capacity required - * @return {BatchBuffer} - buffer than can hold atleast `size` floats + * @return {ViewableBuffer} - buffer than can hold atleast `size` floats * @private */ getAttributeBuffer(size) @@ -599,7 +596,7 @@ if (!buffer) { - this._aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertexSize * 4); + this._aBuffers[roundedSize] = buffer = new ViewableBuffer(roundedSize * this.vertexSize * 4); } return buffer; @@ -643,14 +640,12 @@ * element into the index buffer. * * @param {PIXI.Sprite} element - element being rendered - * @param {Float32Array} float32View - float32-view of the attribute buffer - * @param {Uint32Array} uint32View - uint32-view of the attribute buffer + * @param {PIXI.ViewableBuffer} attributeBuffer - attribute buffer * @param {Uint16Array} indexBuffer - index buffer * @param {number} aIndex - number of floats already in the attribute buffer * @param {number} iIndex - number of indices already in `indexBuffer` */ - packInterleavedGeometry(element, - float32View, uint32View, indexBuffer, aIndex, iIndex) + packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) { const packedVerticies = aIndex / this.vertexSize; const indicies = element.indices; @@ -715,23 +710,29 @@ if (!source)// Only aTextureId has no source! { - float32View[aIndex++] = textureId; + attributeBuffer.float32View[aIndex++] = textureId; continue; } - let offset = sourceOffsets[s]; - const size = (typeof attribute !== 'string') - ? attribute.size : builtinAttributeSizes[attribute]; + const isBuiltin = (typeof attribute === 'string'); + const type = (isBuiltin) ? builtinAttributeDefinitions[attribute].type + : attribute.type; + const size = (isBuiltin) ? builtinAttributeDefinitions[attribute].size + : attribute.size; + const wordSize = (isBuiltin) ? builtinAttributeDefinitions[attribute]._wordSize + : attribute._wordSize;// size of each attribute in words + const typeWordSize = wordSize / size;// size of type in words - for (let float = 0; float < size; float++) + let offset = sourceOffsets[s]; + let globalOffset = aIndex / typeWordSize; + + for (let localOffset = 0; localOffset < size; localOffset++) { - if (attribute === 'aColor') - { uint32View[aIndex++] = source[offset++ % source.length]; } - else - { float32View[aIndex++] = source[offset++ % source.length]; } + attributeBuffer.view(type)[globalOffset++] = source[offset++ % source.length]; } sourceOffsets[s] = offset; + aIndex = globalOffset * typeWordSize; } } diff --git a/packages/core/src/batch/BatchBuffer.js b/packages/core/src/batch/BatchBuffer.js deleted file mode 100644 index 5ee8a5f..0000000 --- a/packages/core/src/batch/BatchBuffer.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * `ArrayBuffer` wrapper with float32 and uint32 views. It - * is used by `AbstractBatchRenderer` to store interleaved - * object geometries. - * - * @class - * @memberof PIXI - */ -export default class BatchBuffer -{ - /** - * @param {number} size - The size of the buffer in bytes. - */ - constructor(size) - { - /** - * Underlying `ArrayBuffer` that holds all the data - * and is of capacity `size`. - * - * @member {ArrayBuffer} - */ - this.rawBinaryData = new ArrayBuffer(size); - - /** - * View on the raw binary data as a `Float32Array`. - * - * @member {Float32Array} - */ - this.float32View = new Float32Array(this.rawBinaryData); - - /** - * View on the raw binary data as a `Uint32Array`. - * - * @member {Uint32Array} - */ - this.uint32View = new Uint32Array(this.rawBinaryData); - } - - /** - * Destroys all buffer references. - */ - destroy() - { - this.rawBinaryData = null; - this.float32View = null; - this.uint32View = null; - } -} diff --git a/packages/core/src/batch/BatchPluginFactory.js b/packages/core/src/batch/BatchPluginFactory.js index 91c044e..455e29a 100644 --- a/packages/core/src/batch/BatchPluginFactory.js +++ b/packages/core/src/batch/BatchPluginFactory.js @@ -1,6 +1,8 @@ import BatchShaderGenerator from './BatchShaderGenerator'; import BatchGeometry from './BatchGeometry'; import AbstractBatchRenderer from './AbstractBatchRenderer'; +import ViewableBuffer from '../geometry/ViewableBuffer'; +import { TYPES } from '@pixi/constants'; import defaultVertex from './texture.vert'; import defaultFragment from './texture.frag'; @@ -52,8 +54,22 @@ vertex, } = Object.assign({ attributeDefinitions: [ - { property: 'vertexData', name: 'aVertexPosition', size: 2 }, - { property: 'uvs', name: 'aTextureCoord', size: 2 }, + { + property: 'vertexData', + name: 'aVertexPosition', + type: 'float32', + size: 2, + glType: TYPES.FLOAT, + glSize: 2, + }, + { + property: 'uvs', + name: 'aTextureCoord', + type: 'float32', + size: 2, + glType: TYPES.FLOAT, + glSize: 2, + }, 'aColor', // built-in attribute 'aTextureId', ], @@ -62,6 +78,8 @@ geometryClass: BatchGeometry, }, options); + BatchPluginFactory._checkAttributeDefinitionCompatibility(attributeDefinitions); + const vertexSize = AbstractBatchRenderer.vertexSizeOf(attributeDefinitions); return class BatchPlugin extends AbstractBatchRenderer @@ -101,6 +119,39 @@ { return defaultFragment; } + + static _checkAttributeDefinitionCompatibility(definitions) + { + definitions.forEach((def) => + { + if (typeof def === 'string') + { + return;// built-in attribute + } + + const inputSize = ViewableBuffer.sizeOf(def.type) * def.size; + + if (inputSize % 4 !== 0) + { + throw new Error('Batch rendering requires that your object ' + + 'attributes be of net size multiple of four. The attribute ' + + `${def.property}, a.k.a ${def.name}, has a source size of` + + `${inputSize}, which is not a multiple of 4. Consider padding` + + 'your elements with additional bytes.'); + } + + const outputSize = TYPES.sizeOf(def.glType) * def.glSize; + + if (outputSize !== inputSize) + { + throw new Error('Your object- and gl- types do not match in size.' + + 'The size of each attribute in the object property array is ' + + `${inputSize}, while the buffered size is ${outputSize} in bytes.`); + } + + def._wordSize = inputSize / 4; + }); + } } // Setup the default BatchRenderer plugin, this is what diff --git a/packages/constants/src/index.js b/packages/constants/src/index.js index 64a3a0a..c92ea55 100644 --- a/packages/constants/src/index.js +++ b/packages/constants/src/index.js @@ -212,6 +212,43 @@ UNSIGNED_SHORT_5_5_5_1: 32820, FLOAT: 5126, HALF_FLOAT: 36193, + + /** + * Returns if any type in the `TYPES` enum is of integer + * nature. + * + * @param {number} glint - `TYPES` types + * @return if `glint` is an integral type + */ + isInteger(glint) + { + return (glint !== TYPES.FLOAT) && (glint !== TYPES.HALF_FLOAT); + }, + + /** + * Returns the size of any type in the `TYPES` enum. + * + * @param {number} glint - TYPES enum constant + * @return size of `glint` in bytes + */ + sizeOf(glint) + { + switch (glint) + { + case TYPES.FLOAT: + return 4; + case TYPES.HALF_FLOAT: + case TYPES.UNSIGNED_SHORT_5_5_5_1: + case TYPES.UNSIGNED_SHORT_4_4_4_4: + case TYPES.UNSIGNED_SHORT_5_6_5: + case TYPES.UNSIGNED_SHORT: + return 2; + case TYPES.UNSIGNED_BYTE: + return 1; + default: + throw new Error(`{$glint} isn't a TYPES enum!`); + } + }, }; /** diff --git a/packages/core/src/batch/AbstractBatchRenderer.js b/packages/core/src/batch/AbstractBatchRenderer.js index 8cbefe0..0c41545 100644 --- a/packages/core/src/batch/AbstractBatchRenderer.js +++ b/packages/core/src/batch/AbstractBatchRenderer.js @@ -1,3 +1,4 @@ +import builtinAttributeDefinitions from './utils/builtinAttributeDefinitions'; import BatchDrawCall from './BatchDrawCall'; import BaseTexture from '../textures/BaseTexture'; import builtinAttributeSizes from './utils/builtinAttributeSizes'; @@ -6,7 +7,7 @@ import checkMaxIfStatementsInShader from '../shader/utils/checkMaxIfStatementsInShader'; import { settings } from '@pixi/settings'; import { premultiplyBlendMode, premultiplyTint, nextPow2, log2 } from '@pixi/utils'; -import BatchBuffer from './BatchBuffer'; +import ViewableBuffer from '../geometry/ViewableBuffer'; import { ENV } from '@pixi/constants'; /** @@ -90,12 +91,12 @@ * for each vertex. * * @default - * | Index | AttributeDefinition | - * |-------|--------------------------------------------------------------| - * | 0 | `{ property: vertexData, name: 'aVertexPosition', size: 2 }` | - * | 1 | `{ property: uvs, name: 'aTextureCoord', size: 2 }` | - * | 2 | `'aColor'` | - * | 3 | `'aTextureId'` // mandatory | + * | Index | property | name | type | size | glType | glSize | + * |-------|------------|-----------------|-----------|------|----------------------|--------| + * | 1 | vertexData | aVertexPosition | `float32` | 2 | TYPES.FLOAT | 1 | + * | 2 | uvs | aTextureCoord | `float32` | 2 | TYPES.FLOAT | 1 | + * | 3 | undefined | aColor | `uint32` | 1 | TYPES.UNSIGNED_BYTE | 4 | + * | 4 | undefined | aTextureId | `float32` | 1 | TYPES.FLOAT | 1 | * * @readonly */ @@ -238,7 +239,7 @@ } /** - * Pool of `BatchBuffer` objects that are sorted in + * Pool of `ViewableBuffer` objects that are sorted in * order of increasing size. The flush method uses * the buffer with the least size above the amount * it requires. These are used for passing attributes. @@ -246,7 +247,7 @@ * The first buffer has a size of 8; each subsequent * buffer has double capacity of its previous. * - * @member {PIXI.BatchBuffer} + * @member {PIXI.ViewableBuffer} * @private * @see PIXI.BatchRenderer#getAttributeBuffer */ @@ -379,10 +380,6 @@ vertexSize, } = this; - const { - float32View, uint32View, - } = attrBuffer; - const touch = this.renderer.textureGC.count; let attrIndex = 0; let iIndex = 0; @@ -451,7 +448,7 @@ } } - this.packInterleavedGeometry(sprite, float32View, uint32View, + this.packInterleavedGeometry(sprite, attrBuffer, indexBuffer, attrIndex, iIndex); // push a graphics.. @@ -580,7 +577,7 @@ * can hold atleast `size` floats. * * @param {number} size - minimum capacity required - * @return {BatchBuffer} - buffer than can hold atleast `size` floats + * @return {ViewableBuffer} - buffer than can hold atleast `size` floats * @private */ getAttributeBuffer(size) @@ -599,7 +596,7 @@ if (!buffer) { - this._aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertexSize * 4); + this._aBuffers[roundedSize] = buffer = new ViewableBuffer(roundedSize * this.vertexSize * 4); } return buffer; @@ -643,14 +640,12 @@ * element into the index buffer. * * @param {PIXI.Sprite} element - element being rendered - * @param {Float32Array} float32View - float32-view of the attribute buffer - * @param {Uint32Array} uint32View - uint32-view of the attribute buffer + * @param {PIXI.ViewableBuffer} attributeBuffer - attribute buffer * @param {Uint16Array} indexBuffer - index buffer * @param {number} aIndex - number of floats already in the attribute buffer * @param {number} iIndex - number of indices already in `indexBuffer` */ - packInterleavedGeometry(element, - float32View, uint32View, indexBuffer, aIndex, iIndex) + packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) { const packedVerticies = aIndex / this.vertexSize; const indicies = element.indices; @@ -715,23 +710,29 @@ if (!source)// Only aTextureId has no source! { - float32View[aIndex++] = textureId; + attributeBuffer.float32View[aIndex++] = textureId; continue; } - let offset = sourceOffsets[s]; - const size = (typeof attribute !== 'string') - ? attribute.size : builtinAttributeSizes[attribute]; + const isBuiltin = (typeof attribute === 'string'); + const type = (isBuiltin) ? builtinAttributeDefinitions[attribute].type + : attribute.type; + const size = (isBuiltin) ? builtinAttributeDefinitions[attribute].size + : attribute.size; + const wordSize = (isBuiltin) ? builtinAttributeDefinitions[attribute]._wordSize + : attribute._wordSize;// size of each attribute in words + const typeWordSize = wordSize / size;// size of type in words - for (let float = 0; float < size; float++) + let offset = sourceOffsets[s]; + let globalOffset = aIndex / typeWordSize; + + for (let localOffset = 0; localOffset < size; localOffset++) { - if (attribute === 'aColor') - { uint32View[aIndex++] = source[offset++ % source.length]; } - else - { float32View[aIndex++] = source[offset++ % source.length]; } + attributeBuffer.view(type)[globalOffset++] = source[offset++ % source.length]; } sourceOffsets[s] = offset; + aIndex = globalOffset * typeWordSize; } } diff --git a/packages/core/src/batch/BatchBuffer.js b/packages/core/src/batch/BatchBuffer.js deleted file mode 100644 index 5ee8a5f..0000000 --- a/packages/core/src/batch/BatchBuffer.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * `ArrayBuffer` wrapper with float32 and uint32 views. It - * is used by `AbstractBatchRenderer` to store interleaved - * object geometries. - * - * @class - * @memberof PIXI - */ -export default class BatchBuffer -{ - /** - * @param {number} size - The size of the buffer in bytes. - */ - constructor(size) - { - /** - * Underlying `ArrayBuffer` that holds all the data - * and is of capacity `size`. - * - * @member {ArrayBuffer} - */ - this.rawBinaryData = new ArrayBuffer(size); - - /** - * View on the raw binary data as a `Float32Array`. - * - * @member {Float32Array} - */ - this.float32View = new Float32Array(this.rawBinaryData); - - /** - * View on the raw binary data as a `Uint32Array`. - * - * @member {Uint32Array} - */ - this.uint32View = new Uint32Array(this.rawBinaryData); - } - - /** - * Destroys all buffer references. - */ - destroy() - { - this.rawBinaryData = null; - this.float32View = null; - this.uint32View = null; - } -} diff --git a/packages/core/src/batch/BatchPluginFactory.js b/packages/core/src/batch/BatchPluginFactory.js index 91c044e..455e29a 100644 --- a/packages/core/src/batch/BatchPluginFactory.js +++ b/packages/core/src/batch/BatchPluginFactory.js @@ -1,6 +1,8 @@ import BatchShaderGenerator from './BatchShaderGenerator'; import BatchGeometry from './BatchGeometry'; import AbstractBatchRenderer from './AbstractBatchRenderer'; +import ViewableBuffer from '../geometry/ViewableBuffer'; +import { TYPES } from '@pixi/constants'; import defaultVertex from './texture.vert'; import defaultFragment from './texture.frag'; @@ -52,8 +54,22 @@ vertex, } = Object.assign({ attributeDefinitions: [ - { property: 'vertexData', name: 'aVertexPosition', size: 2 }, - { property: 'uvs', name: 'aTextureCoord', size: 2 }, + { + property: 'vertexData', + name: 'aVertexPosition', + type: 'float32', + size: 2, + glType: TYPES.FLOAT, + glSize: 2, + }, + { + property: 'uvs', + name: 'aTextureCoord', + type: 'float32', + size: 2, + glType: TYPES.FLOAT, + glSize: 2, + }, 'aColor', // built-in attribute 'aTextureId', ], @@ -62,6 +78,8 @@ geometryClass: BatchGeometry, }, options); + BatchPluginFactory._checkAttributeDefinitionCompatibility(attributeDefinitions); + const vertexSize = AbstractBatchRenderer.vertexSizeOf(attributeDefinitions); return class BatchPlugin extends AbstractBatchRenderer @@ -101,6 +119,39 @@ { return defaultFragment; } + + static _checkAttributeDefinitionCompatibility(definitions) + { + definitions.forEach((def) => + { + if (typeof def === 'string') + { + return;// built-in attribute + } + + const inputSize = ViewableBuffer.sizeOf(def.type) * def.size; + + if (inputSize % 4 !== 0) + { + throw new Error('Batch rendering requires that your object ' + + 'attributes be of net size multiple of four. The attribute ' + + `${def.property}, a.k.a ${def.name}, has a source size of` + + `${inputSize}, which is not a multiple of 4. Consider padding` + + 'your elements with additional bytes.'); + } + + const outputSize = TYPES.sizeOf(def.glType) * def.glSize; + + if (outputSize !== inputSize) + { + throw new Error('Your object- and gl- types do not match in size.' + + 'The size of each attribute in the object property array is ' + + `${inputSize}, while the buffered size is ${outputSize} in bytes.`); + } + + def._wordSize = inputSize / 4; + }); + } } // Setup the default BatchRenderer plugin, this is what diff --git a/packages/core/src/batch/utils/builtinAttributeDefinitions.js b/packages/core/src/batch/utils/builtinAttributeDefinitions.js new file mode 100644 index 0000000..edbf531 --- /dev/null +++ b/packages/core/src/batch/utils/builtinAttributeDefinitions.js @@ -0,0 +1,18 @@ +import { TYPES } from '@pixi/constants'; + +export default { + aColor: { + type: 'uint32', + size: 1, + glType: TYPES.UNSIGNED_BYTE, + glSize: 4, + _wordSize: 1, + }, + aTextureId: { + type: 'float32', + size: 1, + glType: TYPES.FLOAT, + glSize: 1, + _wordSize: 1, + }, +}; diff --git a/packages/constants/src/index.js b/packages/constants/src/index.js index 64a3a0a..c92ea55 100644 --- a/packages/constants/src/index.js +++ b/packages/constants/src/index.js @@ -212,6 +212,43 @@ UNSIGNED_SHORT_5_5_5_1: 32820, FLOAT: 5126, HALF_FLOAT: 36193, + + /** + * Returns if any type in the `TYPES` enum is of integer + * nature. + * + * @param {number} glint - `TYPES` types + * @return if `glint` is an integral type + */ + isInteger(glint) + { + return (glint !== TYPES.FLOAT) && (glint !== TYPES.HALF_FLOAT); + }, + + /** + * Returns the size of any type in the `TYPES` enum. + * + * @param {number} glint - TYPES enum constant + * @return size of `glint` in bytes + */ + sizeOf(glint) + { + switch (glint) + { + case TYPES.FLOAT: + return 4; + case TYPES.HALF_FLOAT: + case TYPES.UNSIGNED_SHORT_5_5_5_1: + case TYPES.UNSIGNED_SHORT_4_4_4_4: + case TYPES.UNSIGNED_SHORT_5_6_5: + case TYPES.UNSIGNED_SHORT: + return 2; + case TYPES.UNSIGNED_BYTE: + return 1; + default: + throw new Error(`{$glint} isn't a TYPES enum!`); + } + }, }; /** diff --git a/packages/core/src/batch/AbstractBatchRenderer.js b/packages/core/src/batch/AbstractBatchRenderer.js index 8cbefe0..0c41545 100644 --- a/packages/core/src/batch/AbstractBatchRenderer.js +++ b/packages/core/src/batch/AbstractBatchRenderer.js @@ -1,3 +1,4 @@ +import builtinAttributeDefinitions from './utils/builtinAttributeDefinitions'; import BatchDrawCall from './BatchDrawCall'; import BaseTexture from '../textures/BaseTexture'; import builtinAttributeSizes from './utils/builtinAttributeSizes'; @@ -6,7 +7,7 @@ import checkMaxIfStatementsInShader from '../shader/utils/checkMaxIfStatementsInShader'; import { settings } from '@pixi/settings'; import { premultiplyBlendMode, premultiplyTint, nextPow2, log2 } from '@pixi/utils'; -import BatchBuffer from './BatchBuffer'; +import ViewableBuffer from '../geometry/ViewableBuffer'; import { ENV } from '@pixi/constants'; /** @@ -90,12 +91,12 @@ * for each vertex. * * @default - * | Index | AttributeDefinition | - * |-------|--------------------------------------------------------------| - * | 0 | `{ property: vertexData, name: 'aVertexPosition', size: 2 }` | - * | 1 | `{ property: uvs, name: 'aTextureCoord', size: 2 }` | - * | 2 | `'aColor'` | - * | 3 | `'aTextureId'` // mandatory | + * | Index | property | name | type | size | glType | glSize | + * |-------|------------|-----------------|-----------|------|----------------------|--------| + * | 1 | vertexData | aVertexPosition | `float32` | 2 | TYPES.FLOAT | 1 | + * | 2 | uvs | aTextureCoord | `float32` | 2 | TYPES.FLOAT | 1 | + * | 3 | undefined | aColor | `uint32` | 1 | TYPES.UNSIGNED_BYTE | 4 | + * | 4 | undefined | aTextureId | `float32` | 1 | TYPES.FLOAT | 1 | * * @readonly */ @@ -238,7 +239,7 @@ } /** - * Pool of `BatchBuffer` objects that are sorted in + * Pool of `ViewableBuffer` objects that are sorted in * order of increasing size. The flush method uses * the buffer with the least size above the amount * it requires. These are used for passing attributes. @@ -246,7 +247,7 @@ * The first buffer has a size of 8; each subsequent * buffer has double capacity of its previous. * - * @member {PIXI.BatchBuffer} + * @member {PIXI.ViewableBuffer} * @private * @see PIXI.BatchRenderer#getAttributeBuffer */ @@ -379,10 +380,6 @@ vertexSize, } = this; - const { - float32View, uint32View, - } = attrBuffer; - const touch = this.renderer.textureGC.count; let attrIndex = 0; let iIndex = 0; @@ -451,7 +448,7 @@ } } - this.packInterleavedGeometry(sprite, float32View, uint32View, + this.packInterleavedGeometry(sprite, attrBuffer, indexBuffer, attrIndex, iIndex); // push a graphics.. @@ -580,7 +577,7 @@ * can hold atleast `size` floats. * * @param {number} size - minimum capacity required - * @return {BatchBuffer} - buffer than can hold atleast `size` floats + * @return {ViewableBuffer} - buffer than can hold atleast `size` floats * @private */ getAttributeBuffer(size) @@ -599,7 +596,7 @@ if (!buffer) { - this._aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertexSize * 4); + this._aBuffers[roundedSize] = buffer = new ViewableBuffer(roundedSize * this.vertexSize * 4); } return buffer; @@ -643,14 +640,12 @@ * element into the index buffer. * * @param {PIXI.Sprite} element - element being rendered - * @param {Float32Array} float32View - float32-view of the attribute buffer - * @param {Uint32Array} uint32View - uint32-view of the attribute buffer + * @param {PIXI.ViewableBuffer} attributeBuffer - attribute buffer * @param {Uint16Array} indexBuffer - index buffer * @param {number} aIndex - number of floats already in the attribute buffer * @param {number} iIndex - number of indices already in `indexBuffer` */ - packInterleavedGeometry(element, - float32View, uint32View, indexBuffer, aIndex, iIndex) + packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) { const packedVerticies = aIndex / this.vertexSize; const indicies = element.indices; @@ -715,23 +710,29 @@ if (!source)// Only aTextureId has no source! { - float32View[aIndex++] = textureId; + attributeBuffer.float32View[aIndex++] = textureId; continue; } - let offset = sourceOffsets[s]; - const size = (typeof attribute !== 'string') - ? attribute.size : builtinAttributeSizes[attribute]; + const isBuiltin = (typeof attribute === 'string'); + const type = (isBuiltin) ? builtinAttributeDefinitions[attribute].type + : attribute.type; + const size = (isBuiltin) ? builtinAttributeDefinitions[attribute].size + : attribute.size; + const wordSize = (isBuiltin) ? builtinAttributeDefinitions[attribute]._wordSize + : attribute._wordSize;// size of each attribute in words + const typeWordSize = wordSize / size;// size of type in words - for (let float = 0; float < size; float++) + let offset = sourceOffsets[s]; + let globalOffset = aIndex / typeWordSize; + + for (let localOffset = 0; localOffset < size; localOffset++) { - if (attribute === 'aColor') - { uint32View[aIndex++] = source[offset++ % source.length]; } - else - { float32View[aIndex++] = source[offset++ % source.length]; } + attributeBuffer.view(type)[globalOffset++] = source[offset++ % source.length]; } sourceOffsets[s] = offset; + aIndex = globalOffset * typeWordSize; } } diff --git a/packages/core/src/batch/BatchBuffer.js b/packages/core/src/batch/BatchBuffer.js deleted file mode 100644 index 5ee8a5f..0000000 --- a/packages/core/src/batch/BatchBuffer.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * `ArrayBuffer` wrapper with float32 and uint32 views. It - * is used by `AbstractBatchRenderer` to store interleaved - * object geometries. - * - * @class - * @memberof PIXI - */ -export default class BatchBuffer -{ - /** - * @param {number} size - The size of the buffer in bytes. - */ - constructor(size) - { - /** - * Underlying `ArrayBuffer` that holds all the data - * and is of capacity `size`. - * - * @member {ArrayBuffer} - */ - this.rawBinaryData = new ArrayBuffer(size); - - /** - * View on the raw binary data as a `Float32Array`. - * - * @member {Float32Array} - */ - this.float32View = new Float32Array(this.rawBinaryData); - - /** - * View on the raw binary data as a `Uint32Array`. - * - * @member {Uint32Array} - */ - this.uint32View = new Uint32Array(this.rawBinaryData); - } - - /** - * Destroys all buffer references. - */ - destroy() - { - this.rawBinaryData = null; - this.float32View = null; - this.uint32View = null; - } -} diff --git a/packages/core/src/batch/BatchPluginFactory.js b/packages/core/src/batch/BatchPluginFactory.js index 91c044e..455e29a 100644 --- a/packages/core/src/batch/BatchPluginFactory.js +++ b/packages/core/src/batch/BatchPluginFactory.js @@ -1,6 +1,8 @@ import BatchShaderGenerator from './BatchShaderGenerator'; import BatchGeometry from './BatchGeometry'; import AbstractBatchRenderer from './AbstractBatchRenderer'; +import ViewableBuffer from '../geometry/ViewableBuffer'; +import { TYPES } from '@pixi/constants'; import defaultVertex from './texture.vert'; import defaultFragment from './texture.frag'; @@ -52,8 +54,22 @@ vertex, } = Object.assign({ attributeDefinitions: [ - { property: 'vertexData', name: 'aVertexPosition', size: 2 }, - { property: 'uvs', name: 'aTextureCoord', size: 2 }, + { + property: 'vertexData', + name: 'aVertexPosition', + type: 'float32', + size: 2, + glType: TYPES.FLOAT, + glSize: 2, + }, + { + property: 'uvs', + name: 'aTextureCoord', + type: 'float32', + size: 2, + glType: TYPES.FLOAT, + glSize: 2, + }, 'aColor', // built-in attribute 'aTextureId', ], @@ -62,6 +78,8 @@ geometryClass: BatchGeometry, }, options); + BatchPluginFactory._checkAttributeDefinitionCompatibility(attributeDefinitions); + const vertexSize = AbstractBatchRenderer.vertexSizeOf(attributeDefinitions); return class BatchPlugin extends AbstractBatchRenderer @@ -101,6 +119,39 @@ { return defaultFragment; } + + static _checkAttributeDefinitionCompatibility(definitions) + { + definitions.forEach((def) => + { + if (typeof def === 'string') + { + return;// built-in attribute + } + + const inputSize = ViewableBuffer.sizeOf(def.type) * def.size; + + if (inputSize % 4 !== 0) + { + throw new Error('Batch rendering requires that your object ' + + 'attributes be of net size multiple of four. The attribute ' + + `${def.property}, a.k.a ${def.name}, has a source size of` + + `${inputSize}, which is not a multiple of 4. Consider padding` + + 'your elements with additional bytes.'); + } + + const outputSize = TYPES.sizeOf(def.glType) * def.glSize; + + if (outputSize !== inputSize) + { + throw new Error('Your object- and gl- types do not match in size.' + + 'The size of each attribute in the object property array is ' + + `${inputSize}, while the buffered size is ${outputSize} in bytes.`); + } + + def._wordSize = inputSize / 4; + }); + } } // Setup the default BatchRenderer plugin, this is what diff --git a/packages/core/src/batch/utils/builtinAttributeDefinitions.js b/packages/core/src/batch/utils/builtinAttributeDefinitions.js new file mode 100644 index 0000000..edbf531 --- /dev/null +++ b/packages/core/src/batch/utils/builtinAttributeDefinitions.js @@ -0,0 +1,18 @@ +import { TYPES } from '@pixi/constants'; + +export default { + aColor: { + type: 'uint32', + size: 1, + glType: TYPES.UNSIGNED_BYTE, + glSize: 4, + _wordSize: 1, + }, + aTextureId: { + type: 'float32', + size: 1, + glType: TYPES.FLOAT, + glSize: 1, + _wordSize: 1, + }, +}; diff --git a/packages/core/src/batch/utils/builtinAttributeSizes.js b/packages/core/src/batch/utils/builtinAttributeSizes.js new file mode 100644 index 0000000..2ffb06f --- /dev/null +++ b/packages/core/src/batch/utils/builtinAttributeSizes.js @@ -0,0 +1,13 @@ +/** + * Sizes of built-in attributes provided by `BatchRenderer`, + * which includes the required `aTextureId` attribute. + * + * @type Map + * @see {AbstractBatchRenderer#vertexSizeOf} + */ +export const builtinAttributeSizes = { + aColor: 1, + aTextureId: 1, +}; + +export default builtinAttributeSizes; diff --git a/packages/constants/src/index.js b/packages/constants/src/index.js index 64a3a0a..c92ea55 100644 --- a/packages/constants/src/index.js +++ b/packages/constants/src/index.js @@ -212,6 +212,43 @@ UNSIGNED_SHORT_5_5_5_1: 32820, FLOAT: 5126, HALF_FLOAT: 36193, + + /** + * Returns if any type in the `TYPES` enum is of integer + * nature. + * + * @param {number} glint - `TYPES` types + * @return if `glint` is an integral type + */ + isInteger(glint) + { + return (glint !== TYPES.FLOAT) && (glint !== TYPES.HALF_FLOAT); + }, + + /** + * Returns the size of any type in the `TYPES` enum. + * + * @param {number} glint - TYPES enum constant + * @return size of `glint` in bytes + */ + sizeOf(glint) + { + switch (glint) + { + case TYPES.FLOAT: + return 4; + case TYPES.HALF_FLOAT: + case TYPES.UNSIGNED_SHORT_5_5_5_1: + case TYPES.UNSIGNED_SHORT_4_4_4_4: + case TYPES.UNSIGNED_SHORT_5_6_5: + case TYPES.UNSIGNED_SHORT: + return 2; + case TYPES.UNSIGNED_BYTE: + return 1; + default: + throw new Error(`{$glint} isn't a TYPES enum!`); + } + }, }; /** diff --git a/packages/core/src/batch/AbstractBatchRenderer.js b/packages/core/src/batch/AbstractBatchRenderer.js index 8cbefe0..0c41545 100644 --- a/packages/core/src/batch/AbstractBatchRenderer.js +++ b/packages/core/src/batch/AbstractBatchRenderer.js @@ -1,3 +1,4 @@ +import builtinAttributeDefinitions from './utils/builtinAttributeDefinitions'; import BatchDrawCall from './BatchDrawCall'; import BaseTexture from '../textures/BaseTexture'; import builtinAttributeSizes from './utils/builtinAttributeSizes'; @@ -6,7 +7,7 @@ import checkMaxIfStatementsInShader from '../shader/utils/checkMaxIfStatementsInShader'; import { settings } from '@pixi/settings'; import { premultiplyBlendMode, premultiplyTint, nextPow2, log2 } from '@pixi/utils'; -import BatchBuffer from './BatchBuffer'; +import ViewableBuffer from '../geometry/ViewableBuffer'; import { ENV } from '@pixi/constants'; /** @@ -90,12 +91,12 @@ * for each vertex. * * @default - * | Index | AttributeDefinition | - * |-------|--------------------------------------------------------------| - * | 0 | `{ property: vertexData, name: 'aVertexPosition', size: 2 }` | - * | 1 | `{ property: uvs, name: 'aTextureCoord', size: 2 }` | - * | 2 | `'aColor'` | - * | 3 | `'aTextureId'` // mandatory | + * | Index | property | name | type | size | glType | glSize | + * |-------|------------|-----------------|-----------|------|----------------------|--------| + * | 1 | vertexData | aVertexPosition | `float32` | 2 | TYPES.FLOAT | 1 | + * | 2 | uvs | aTextureCoord | `float32` | 2 | TYPES.FLOAT | 1 | + * | 3 | undefined | aColor | `uint32` | 1 | TYPES.UNSIGNED_BYTE | 4 | + * | 4 | undefined | aTextureId | `float32` | 1 | TYPES.FLOAT | 1 | * * @readonly */ @@ -238,7 +239,7 @@ } /** - * Pool of `BatchBuffer` objects that are sorted in + * Pool of `ViewableBuffer` objects that are sorted in * order of increasing size. The flush method uses * the buffer with the least size above the amount * it requires. These are used for passing attributes. @@ -246,7 +247,7 @@ * The first buffer has a size of 8; each subsequent * buffer has double capacity of its previous. * - * @member {PIXI.BatchBuffer} + * @member {PIXI.ViewableBuffer} * @private * @see PIXI.BatchRenderer#getAttributeBuffer */ @@ -379,10 +380,6 @@ vertexSize, } = this; - const { - float32View, uint32View, - } = attrBuffer; - const touch = this.renderer.textureGC.count; let attrIndex = 0; let iIndex = 0; @@ -451,7 +448,7 @@ } } - this.packInterleavedGeometry(sprite, float32View, uint32View, + this.packInterleavedGeometry(sprite, attrBuffer, indexBuffer, attrIndex, iIndex); // push a graphics.. @@ -580,7 +577,7 @@ * can hold atleast `size` floats. * * @param {number} size - minimum capacity required - * @return {BatchBuffer} - buffer than can hold atleast `size` floats + * @return {ViewableBuffer} - buffer than can hold atleast `size` floats * @private */ getAttributeBuffer(size) @@ -599,7 +596,7 @@ if (!buffer) { - this._aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertexSize * 4); + this._aBuffers[roundedSize] = buffer = new ViewableBuffer(roundedSize * this.vertexSize * 4); } return buffer; @@ -643,14 +640,12 @@ * element into the index buffer. * * @param {PIXI.Sprite} element - element being rendered - * @param {Float32Array} float32View - float32-view of the attribute buffer - * @param {Uint32Array} uint32View - uint32-view of the attribute buffer + * @param {PIXI.ViewableBuffer} attributeBuffer - attribute buffer * @param {Uint16Array} indexBuffer - index buffer * @param {number} aIndex - number of floats already in the attribute buffer * @param {number} iIndex - number of indices already in `indexBuffer` */ - packInterleavedGeometry(element, - float32View, uint32View, indexBuffer, aIndex, iIndex) + packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) { const packedVerticies = aIndex / this.vertexSize; const indicies = element.indices; @@ -715,23 +710,29 @@ if (!source)// Only aTextureId has no source! { - float32View[aIndex++] = textureId; + attributeBuffer.float32View[aIndex++] = textureId; continue; } - let offset = sourceOffsets[s]; - const size = (typeof attribute !== 'string') - ? attribute.size : builtinAttributeSizes[attribute]; + const isBuiltin = (typeof attribute === 'string'); + const type = (isBuiltin) ? builtinAttributeDefinitions[attribute].type + : attribute.type; + const size = (isBuiltin) ? builtinAttributeDefinitions[attribute].size + : attribute.size; + const wordSize = (isBuiltin) ? builtinAttributeDefinitions[attribute]._wordSize + : attribute._wordSize;// size of each attribute in words + const typeWordSize = wordSize / size;// size of type in words - for (let float = 0; float < size; float++) + let offset = sourceOffsets[s]; + let globalOffset = aIndex / typeWordSize; + + for (let localOffset = 0; localOffset < size; localOffset++) { - if (attribute === 'aColor') - { uint32View[aIndex++] = source[offset++ % source.length]; } - else - { float32View[aIndex++] = source[offset++ % source.length]; } + attributeBuffer.view(type)[globalOffset++] = source[offset++ % source.length]; } sourceOffsets[s] = offset; + aIndex = globalOffset * typeWordSize; } } diff --git a/packages/core/src/batch/BatchBuffer.js b/packages/core/src/batch/BatchBuffer.js deleted file mode 100644 index 5ee8a5f..0000000 --- a/packages/core/src/batch/BatchBuffer.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * `ArrayBuffer` wrapper with float32 and uint32 views. It - * is used by `AbstractBatchRenderer` to store interleaved - * object geometries. - * - * @class - * @memberof PIXI - */ -export default class BatchBuffer -{ - /** - * @param {number} size - The size of the buffer in bytes. - */ - constructor(size) - { - /** - * Underlying `ArrayBuffer` that holds all the data - * and is of capacity `size`. - * - * @member {ArrayBuffer} - */ - this.rawBinaryData = new ArrayBuffer(size); - - /** - * View on the raw binary data as a `Float32Array`. - * - * @member {Float32Array} - */ - this.float32View = new Float32Array(this.rawBinaryData); - - /** - * View on the raw binary data as a `Uint32Array`. - * - * @member {Uint32Array} - */ - this.uint32View = new Uint32Array(this.rawBinaryData); - } - - /** - * Destroys all buffer references. - */ - destroy() - { - this.rawBinaryData = null; - this.float32View = null; - this.uint32View = null; - } -} diff --git a/packages/core/src/batch/BatchPluginFactory.js b/packages/core/src/batch/BatchPluginFactory.js index 91c044e..455e29a 100644 --- a/packages/core/src/batch/BatchPluginFactory.js +++ b/packages/core/src/batch/BatchPluginFactory.js @@ -1,6 +1,8 @@ import BatchShaderGenerator from './BatchShaderGenerator'; import BatchGeometry from './BatchGeometry'; import AbstractBatchRenderer from './AbstractBatchRenderer'; +import ViewableBuffer from '../geometry/ViewableBuffer'; +import { TYPES } from '@pixi/constants'; import defaultVertex from './texture.vert'; import defaultFragment from './texture.frag'; @@ -52,8 +54,22 @@ vertex, } = Object.assign({ attributeDefinitions: [ - { property: 'vertexData', name: 'aVertexPosition', size: 2 }, - { property: 'uvs', name: 'aTextureCoord', size: 2 }, + { + property: 'vertexData', + name: 'aVertexPosition', + type: 'float32', + size: 2, + glType: TYPES.FLOAT, + glSize: 2, + }, + { + property: 'uvs', + name: 'aTextureCoord', + type: 'float32', + size: 2, + glType: TYPES.FLOAT, + glSize: 2, + }, 'aColor', // built-in attribute 'aTextureId', ], @@ -62,6 +78,8 @@ geometryClass: BatchGeometry, }, options); + BatchPluginFactory._checkAttributeDefinitionCompatibility(attributeDefinitions); + const vertexSize = AbstractBatchRenderer.vertexSizeOf(attributeDefinitions); return class BatchPlugin extends AbstractBatchRenderer @@ -101,6 +119,39 @@ { return defaultFragment; } + + static _checkAttributeDefinitionCompatibility(definitions) + { + definitions.forEach((def) => + { + if (typeof def === 'string') + { + return;// built-in attribute + } + + const inputSize = ViewableBuffer.sizeOf(def.type) * def.size; + + if (inputSize % 4 !== 0) + { + throw new Error('Batch rendering requires that your object ' + + 'attributes be of net size multiple of four. The attribute ' + + `${def.property}, a.k.a ${def.name}, has a source size of` + + `${inputSize}, which is not a multiple of 4. Consider padding` + + 'your elements with additional bytes.'); + } + + const outputSize = TYPES.sizeOf(def.glType) * def.glSize; + + if (outputSize !== inputSize) + { + throw new Error('Your object- and gl- types do not match in size.' + + 'The size of each attribute in the object property array is ' + + `${inputSize}, while the buffered size is ${outputSize} in bytes.`); + } + + def._wordSize = inputSize / 4; + }); + } } // Setup the default BatchRenderer plugin, this is what diff --git a/packages/core/src/batch/utils/builtinAttributeDefinitions.js b/packages/core/src/batch/utils/builtinAttributeDefinitions.js new file mode 100644 index 0000000..edbf531 --- /dev/null +++ b/packages/core/src/batch/utils/builtinAttributeDefinitions.js @@ -0,0 +1,18 @@ +import { TYPES } from '@pixi/constants'; + +export default { + aColor: { + type: 'uint32', + size: 1, + glType: TYPES.UNSIGNED_BYTE, + glSize: 4, + _wordSize: 1, + }, + aTextureId: { + type: 'float32', + size: 1, + glType: TYPES.FLOAT, + glSize: 1, + _wordSize: 1, + }, +}; diff --git a/packages/core/src/batch/utils/builtinAttributeSizes.js b/packages/core/src/batch/utils/builtinAttributeSizes.js new file mode 100644 index 0000000..2ffb06f --- /dev/null +++ b/packages/core/src/batch/utils/builtinAttributeSizes.js @@ -0,0 +1,13 @@ +/** + * Sizes of built-in attributes provided by `BatchRenderer`, + * which includes the required `aTextureId` attribute. + * + * @type Map + * @see {AbstractBatchRenderer#vertexSizeOf} + */ +export const builtinAttributeSizes = { + aColor: 1, + aTextureId: 1, +}; + +export default builtinAttributeSizes; diff --git a/packages/core/src/geometry/ViewableBuffer.js b/packages/core/src/geometry/ViewableBuffer.js new file mode 100644 index 0000000..793a425 --- /dev/null +++ b/packages/core/src/geometry/ViewableBuffer.js @@ -0,0 +1,168 @@ +/** + * Flexible wrapper around `ArrayBuffer` that also provides + * typed array views on demand. + * + * @class + * @memberof PIXI + */ +export default class ViewableBuffer +{ + /** + * @param {number} size - The size of the buffer in bytes. + */ + constructor(size) + { + /** + * Underlying `ArrayBuffer` that holds all the data + * and is of capacity `size`. + * + * @member {ArrayBuffer} + */ + this.rawBinaryData = new ArrayBuffer(size); + } + + /** + * View on the raw binary data as a `Int8Array`. + * + * @member {Int8Array} + */ + get int8View() + { + if (!this._int8View) + { + this._int8View = new Int8Array(this.rawBinaryData); + } + + return this._int8View; + } + + /** + * View on the raw binary data as a `Uint8Array`. + * + * @member {Uint8Array} + */ + get uint8View() + { + if (!this._uint8View) + { + this._uint8View = new Uint8Array(this.rawBinaryData); + } + + return this._uint8View; + } + + /** + * View on the raw binary data as a `Int16Array`. + * + * @member {Int16Array} + */ + get int16View() + { + if (!this._int16View) + { + this._int16View = new Int16Array(this.rawBinaryData); + } + + return this._int16View; + } + + /** + * View on the raw binary data as a `Uint16Array`. + * + * @member {Uint16Array} + */ + get uint16View() + { + if (!this._uint16View) + { + this._uint16View = new Uint16Array(this.rawBinaryData); + } + + return this._uint16View; + } + + /** + * View on the raw binary data as a `Int32Array`. + * + * @member {Int32Array} + */ + get int32View() + { + if (!this._int32View) + { + this._int32View = new Int32Array(this.rawBinaryData); + } + + return this._int32View; + } + + /** + * View on the raw binary data as a `Uint32Array`. + * + * @member {Float32Array} + */ + get uint32View() + { + if (!this._uint32View) + { + this._uint32View = new Uint32Array(this.rawBinaryData); + } + + return this._uint32View; + } + + /** + * View on the raw binary data as a `Float32Array`. + * + * @member {Float32Array} + */ + get float32View() + { + if (!this._float32View) + { + this._float32View = new Float32Array(this.rawBinaryData); + } + + return this._float32View; + } + + view(type) + { + return this[`${type}View`]; + } + + /** + * Destroys all buffer references. Do not use after calling + * this. + */ + destroy() + { + this.rawBinaryData = null; + this._int8View = null; + this._uint8View = null; + this._int16View = null; + this._uint16View = null; + this._int32View = null; + this._uint32View = null; + this._float32View = null; + } + + static sizeOf(type) + { + switch (type) + { + case 'int8': + case 'uint8': + return 1; + case 'int16': + case 'uint16': + return 2; + case 'int32': + case 'uint32': + case 'float32': + return 4; + default: + throw new Error(`${type} isn't a valid view type`); + } + } +} diff --git a/packages/constants/src/index.js b/packages/constants/src/index.js index 64a3a0a..c92ea55 100644 --- a/packages/constants/src/index.js +++ b/packages/constants/src/index.js @@ -212,6 +212,43 @@ UNSIGNED_SHORT_5_5_5_1: 32820, FLOAT: 5126, HALF_FLOAT: 36193, + + /** + * Returns if any type in the `TYPES` enum is of integer + * nature. + * + * @param {number} glint - `TYPES` types + * @return if `glint` is an integral type + */ + isInteger(glint) + { + return (glint !== TYPES.FLOAT) && (glint !== TYPES.HALF_FLOAT); + }, + + /** + * Returns the size of any type in the `TYPES` enum. + * + * @param {number} glint - TYPES enum constant + * @return size of `glint` in bytes + */ + sizeOf(glint) + { + switch (glint) + { + case TYPES.FLOAT: + return 4; + case TYPES.HALF_FLOAT: + case TYPES.UNSIGNED_SHORT_5_5_5_1: + case TYPES.UNSIGNED_SHORT_4_4_4_4: + case TYPES.UNSIGNED_SHORT_5_6_5: + case TYPES.UNSIGNED_SHORT: + return 2; + case TYPES.UNSIGNED_BYTE: + return 1; + default: + throw new Error(`{$glint} isn't a TYPES enum!`); + } + }, }; /** diff --git a/packages/core/src/batch/AbstractBatchRenderer.js b/packages/core/src/batch/AbstractBatchRenderer.js index 8cbefe0..0c41545 100644 --- a/packages/core/src/batch/AbstractBatchRenderer.js +++ b/packages/core/src/batch/AbstractBatchRenderer.js @@ -1,3 +1,4 @@ +import builtinAttributeDefinitions from './utils/builtinAttributeDefinitions'; import BatchDrawCall from './BatchDrawCall'; import BaseTexture from '../textures/BaseTexture'; import builtinAttributeSizes from './utils/builtinAttributeSizes'; @@ -6,7 +7,7 @@ import checkMaxIfStatementsInShader from '../shader/utils/checkMaxIfStatementsInShader'; import { settings } from '@pixi/settings'; import { premultiplyBlendMode, premultiplyTint, nextPow2, log2 } from '@pixi/utils'; -import BatchBuffer from './BatchBuffer'; +import ViewableBuffer from '../geometry/ViewableBuffer'; import { ENV } from '@pixi/constants'; /** @@ -90,12 +91,12 @@ * for each vertex. * * @default - * | Index | AttributeDefinition | - * |-------|--------------------------------------------------------------| - * | 0 | `{ property: vertexData, name: 'aVertexPosition', size: 2 }` | - * | 1 | `{ property: uvs, name: 'aTextureCoord', size: 2 }` | - * | 2 | `'aColor'` | - * | 3 | `'aTextureId'` // mandatory | + * | Index | property | name | type | size | glType | glSize | + * |-------|------------|-----------------|-----------|------|----------------------|--------| + * | 1 | vertexData | aVertexPosition | `float32` | 2 | TYPES.FLOAT | 1 | + * | 2 | uvs | aTextureCoord | `float32` | 2 | TYPES.FLOAT | 1 | + * | 3 | undefined | aColor | `uint32` | 1 | TYPES.UNSIGNED_BYTE | 4 | + * | 4 | undefined | aTextureId | `float32` | 1 | TYPES.FLOAT | 1 | * * @readonly */ @@ -238,7 +239,7 @@ } /** - * Pool of `BatchBuffer` objects that are sorted in + * Pool of `ViewableBuffer` objects that are sorted in * order of increasing size. The flush method uses * the buffer with the least size above the amount * it requires. These are used for passing attributes. @@ -246,7 +247,7 @@ * The first buffer has a size of 8; each subsequent * buffer has double capacity of its previous. * - * @member {PIXI.BatchBuffer} + * @member {PIXI.ViewableBuffer} * @private * @see PIXI.BatchRenderer#getAttributeBuffer */ @@ -379,10 +380,6 @@ vertexSize, } = this; - const { - float32View, uint32View, - } = attrBuffer; - const touch = this.renderer.textureGC.count; let attrIndex = 0; let iIndex = 0; @@ -451,7 +448,7 @@ } } - this.packInterleavedGeometry(sprite, float32View, uint32View, + this.packInterleavedGeometry(sprite, attrBuffer, indexBuffer, attrIndex, iIndex); // push a graphics.. @@ -580,7 +577,7 @@ * can hold atleast `size` floats. * * @param {number} size - minimum capacity required - * @return {BatchBuffer} - buffer than can hold atleast `size` floats + * @return {ViewableBuffer} - buffer than can hold atleast `size` floats * @private */ getAttributeBuffer(size) @@ -599,7 +596,7 @@ if (!buffer) { - this._aBuffers[roundedSize] = buffer = new BatchBuffer(roundedSize * this.vertexSize * 4); + this._aBuffers[roundedSize] = buffer = new ViewableBuffer(roundedSize * this.vertexSize * 4); } return buffer; @@ -643,14 +640,12 @@ * element into the index buffer. * * @param {PIXI.Sprite} element - element being rendered - * @param {Float32Array} float32View - float32-view of the attribute buffer - * @param {Uint32Array} uint32View - uint32-view of the attribute buffer + * @param {PIXI.ViewableBuffer} attributeBuffer - attribute buffer * @param {Uint16Array} indexBuffer - index buffer * @param {number} aIndex - number of floats already in the attribute buffer * @param {number} iIndex - number of indices already in `indexBuffer` */ - packInterleavedGeometry(element, - float32View, uint32View, indexBuffer, aIndex, iIndex) + packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) { const packedVerticies = aIndex / this.vertexSize; const indicies = element.indices; @@ -715,23 +710,29 @@ if (!source)// Only aTextureId has no source! { - float32View[aIndex++] = textureId; + attributeBuffer.float32View[aIndex++] = textureId; continue; } - let offset = sourceOffsets[s]; - const size = (typeof attribute !== 'string') - ? attribute.size : builtinAttributeSizes[attribute]; + const isBuiltin = (typeof attribute === 'string'); + const type = (isBuiltin) ? builtinAttributeDefinitions[attribute].type + : attribute.type; + const size = (isBuiltin) ? builtinAttributeDefinitions[attribute].size + : attribute.size; + const wordSize = (isBuiltin) ? builtinAttributeDefinitions[attribute]._wordSize + : attribute._wordSize;// size of each attribute in words + const typeWordSize = wordSize / size;// size of type in words - for (let float = 0; float < size; float++) + let offset = sourceOffsets[s]; + let globalOffset = aIndex / typeWordSize; + + for (let localOffset = 0; localOffset < size; localOffset++) { - if (attribute === 'aColor') - { uint32View[aIndex++] = source[offset++ % source.length]; } - else - { float32View[aIndex++] = source[offset++ % source.length]; } + attributeBuffer.view(type)[globalOffset++] = source[offset++ % source.length]; } sourceOffsets[s] = offset; + aIndex = globalOffset * typeWordSize; } } diff --git a/packages/core/src/batch/BatchBuffer.js b/packages/core/src/batch/BatchBuffer.js deleted file mode 100644 index 5ee8a5f..0000000 --- a/packages/core/src/batch/BatchBuffer.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * `ArrayBuffer` wrapper with float32 and uint32 views. It - * is used by `AbstractBatchRenderer` to store interleaved - * object geometries. - * - * @class - * @memberof PIXI - */ -export default class BatchBuffer -{ - /** - * @param {number} size - The size of the buffer in bytes. - */ - constructor(size) - { - /** - * Underlying `ArrayBuffer` that holds all the data - * and is of capacity `size`. - * - * @member {ArrayBuffer} - */ - this.rawBinaryData = new ArrayBuffer(size); - - /** - * View on the raw binary data as a `Float32Array`. - * - * @member {Float32Array} - */ - this.float32View = new Float32Array(this.rawBinaryData); - - /** - * View on the raw binary data as a `Uint32Array`. - * - * @member {Uint32Array} - */ - this.uint32View = new Uint32Array(this.rawBinaryData); - } - - /** - * Destroys all buffer references. - */ - destroy() - { - this.rawBinaryData = null; - this.float32View = null; - this.uint32View = null; - } -} diff --git a/packages/core/src/batch/BatchPluginFactory.js b/packages/core/src/batch/BatchPluginFactory.js index 91c044e..455e29a 100644 --- a/packages/core/src/batch/BatchPluginFactory.js +++ b/packages/core/src/batch/BatchPluginFactory.js @@ -1,6 +1,8 @@ import BatchShaderGenerator from './BatchShaderGenerator'; import BatchGeometry from './BatchGeometry'; import AbstractBatchRenderer from './AbstractBatchRenderer'; +import ViewableBuffer from '../geometry/ViewableBuffer'; +import { TYPES } from '@pixi/constants'; import defaultVertex from './texture.vert'; import defaultFragment from './texture.frag'; @@ -52,8 +54,22 @@ vertex, } = Object.assign({ attributeDefinitions: [ - { property: 'vertexData', name: 'aVertexPosition', size: 2 }, - { property: 'uvs', name: 'aTextureCoord', size: 2 }, + { + property: 'vertexData', + name: 'aVertexPosition', + type: 'float32', + size: 2, + glType: TYPES.FLOAT, + glSize: 2, + }, + { + property: 'uvs', + name: 'aTextureCoord', + type: 'float32', + size: 2, + glType: TYPES.FLOAT, + glSize: 2, + }, 'aColor', // built-in attribute 'aTextureId', ], @@ -62,6 +78,8 @@ geometryClass: BatchGeometry, }, options); + BatchPluginFactory._checkAttributeDefinitionCompatibility(attributeDefinitions); + const vertexSize = AbstractBatchRenderer.vertexSizeOf(attributeDefinitions); return class BatchPlugin extends AbstractBatchRenderer @@ -101,6 +119,39 @@ { return defaultFragment; } + + static _checkAttributeDefinitionCompatibility(definitions) + { + definitions.forEach((def) => + { + if (typeof def === 'string') + { + return;// built-in attribute + } + + const inputSize = ViewableBuffer.sizeOf(def.type) * def.size; + + if (inputSize % 4 !== 0) + { + throw new Error('Batch rendering requires that your object ' + + 'attributes be of net size multiple of four. The attribute ' + + `${def.property}, a.k.a ${def.name}, has a source size of` + + `${inputSize}, which is not a multiple of 4. Consider padding` + + 'your elements with additional bytes.'); + } + + const outputSize = TYPES.sizeOf(def.glType) * def.glSize; + + if (outputSize !== inputSize) + { + throw new Error('Your object- and gl- types do not match in size.' + + 'The size of each attribute in the object property array is ' + + `${inputSize}, while the buffered size is ${outputSize} in bytes.`); + } + + def._wordSize = inputSize / 4; + }); + } } // Setup the default BatchRenderer plugin, this is what diff --git a/packages/core/src/batch/utils/builtinAttributeDefinitions.js b/packages/core/src/batch/utils/builtinAttributeDefinitions.js new file mode 100644 index 0000000..edbf531 --- /dev/null +++ b/packages/core/src/batch/utils/builtinAttributeDefinitions.js @@ -0,0 +1,18 @@ +import { TYPES } from '@pixi/constants'; + +export default { + aColor: { + type: 'uint32', + size: 1, + glType: TYPES.UNSIGNED_BYTE, + glSize: 4, + _wordSize: 1, + }, + aTextureId: { + type: 'float32', + size: 1, + glType: TYPES.FLOAT, + glSize: 1, + _wordSize: 1, + }, +}; diff --git a/packages/core/src/batch/utils/builtinAttributeSizes.js b/packages/core/src/batch/utils/builtinAttributeSizes.js new file mode 100644 index 0000000..2ffb06f --- /dev/null +++ b/packages/core/src/batch/utils/builtinAttributeSizes.js @@ -0,0 +1,13 @@ +/** + * Sizes of built-in attributes provided by `BatchRenderer`, + * which includes the required `aTextureId` attribute. + * + * @type Map + * @see {AbstractBatchRenderer#vertexSizeOf} + */ +export const builtinAttributeSizes = { + aColor: 1, + aTextureId: 1, +}; + +export default builtinAttributeSizes; diff --git a/packages/core/src/geometry/ViewableBuffer.js b/packages/core/src/geometry/ViewableBuffer.js new file mode 100644 index 0000000..793a425 --- /dev/null +++ b/packages/core/src/geometry/ViewableBuffer.js @@ -0,0 +1,168 @@ +/** + * Flexible wrapper around `ArrayBuffer` that also provides + * typed array views on demand. + * + * @class + * @memberof PIXI + */ +export default class ViewableBuffer +{ + /** + * @param {number} size - The size of the buffer in bytes. + */ + constructor(size) + { + /** + * Underlying `ArrayBuffer` that holds all the data + * and is of capacity `size`. + * + * @member {ArrayBuffer} + */ + this.rawBinaryData = new ArrayBuffer(size); + } + + /** + * View on the raw binary data as a `Int8Array`. + * + * @member {Int8Array} + */ + get int8View() + { + if (!this._int8View) + { + this._int8View = new Int8Array(this.rawBinaryData); + } + + return this._int8View; + } + + /** + * View on the raw binary data as a `Uint8Array`. + * + * @member {Uint8Array} + */ + get uint8View() + { + if (!this._uint8View) + { + this._uint8View = new Uint8Array(this.rawBinaryData); + } + + return this._uint8View; + } + + /** + * View on the raw binary data as a `Int16Array`. + * + * @member {Int16Array} + */ + get int16View() + { + if (!this._int16View) + { + this._int16View = new Int16Array(this.rawBinaryData); + } + + return this._int16View; + } + + /** + * View on the raw binary data as a `Uint16Array`. + * + * @member {Uint16Array} + */ + get uint16View() + { + if (!this._uint16View) + { + this._uint16View = new Uint16Array(this.rawBinaryData); + } + + return this._uint16View; + } + + /** + * View on the raw binary data as a `Int32Array`. + * + * @member {Int32Array} + */ + get int32View() + { + if (!this._int32View) + { + this._int32View = new Int32Array(this.rawBinaryData); + } + + return this._int32View; + } + + /** + * View on the raw binary data as a `Uint32Array`. + * + * @member {Float32Array} + */ + get uint32View() + { + if (!this._uint32View) + { + this._uint32View = new Uint32Array(this.rawBinaryData); + } + + return this._uint32View; + } + + /** + * View on the raw binary data as a `Float32Array`. + * + * @member {Float32Array} + */ + get float32View() + { + if (!this._float32View) + { + this._float32View = new Float32Array(this.rawBinaryData); + } + + return this._float32View; + } + + view(type) + { + return this[`${type}View`]; + } + + /** + * Destroys all buffer references. Do not use after calling + * this. + */ + destroy() + { + this.rawBinaryData = null; + this._int8View = null; + this._uint8View = null; + this._int16View = null; + this._uint16View = null; + this._int32View = null; + this._uint32View = null; + this._float32View = null; + } + + static sizeOf(type) + { + switch (type) + { + case 'int8': + case 'uint8': + return 1; + case 'int16': + case 'uint16': + return 2; + case 'int32': + case 'uint32': + case 'float32': + return 4; + default: + throw new Error(`${type} isn't a valid view type`); + } + } +} diff --git a/packages/core/src/index.js b/packages/core/src/index.js index d4cb1fa..08ba1c1 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -38,3 +38,4 @@ export { default as Attribute } from './geometry/Attribute'; export { default as Buffer } from './geometry/Buffer'; export { default as Geometry } from './geometry/Geometry'; +export { default as ViewableBuffer } from './geometry/ViewableBuffer';