diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/src/core/renderers/webgl/managers/FramebufferManager.js b/src/core/renderers/webgl/managers/FramebufferManager.js index 645fdc9..42c4556 100644 --- a/src/core/renderers/webgl/managers/FramebufferManager.js +++ b/src/core/renderers/webgl/managers/FramebufferManager.js @@ -20,7 +20,7 @@ * * @private */ - onContextChange() + contextChange() { this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/src/core/renderers/webgl/managers/FramebufferManager.js b/src/core/renderers/webgl/managers/FramebufferManager.js index 645fdc9..42c4556 100644 --- a/src/core/renderers/webgl/managers/FramebufferManager.js +++ b/src/core/renderers/webgl/managers/FramebufferManager.js @@ -20,7 +20,7 @@ * * @private */ - onContextChange() + contextChange() { this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; diff --git a/src/core/renderers/webgl/managers/GeometryManager.js b/src/core/renderers/webgl/managers/GeometryManager.js new file mode 100644 index 0000000..af3da72 --- /dev/null +++ b/src/core/renderers/webgl/managers/GeometryManager.js @@ -0,0 +1,216 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class GeometryManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeVao = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + */ + bind(geometry, glShader) + { + const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + + this.bindVao(vao); + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer._updateID) + { + glBuffer._updateID = buffer._updateID; + // TODO - partial upload?? + + glBuffer.upload(buffer.data, 0); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + * @return {PIXI.glCore.VertexArrayObject} Returns a fresh vao. + */ + initGeometryVao(geometry, glShader) + { + const gl = this.gl; + + this.bindVao(null); + + const vao = this.createVao(); + + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + // first update - and create the buffers! + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[this.CONTEXT_UID]) + { + if (buffer.index) + { + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createVertexBuffer(gl, buffer.data, buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); + } + } + } + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); + } + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const glAttribute = glShader.attributes[j]; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + } + } + + // next update the attributes buffer.. + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + // need to know the shader as it means we can be lazy and let pixi do the work for us.. + // stride, start, type? + vao.addAttribute(glBuffer, + glShader.attributes[j], + attribute.type || 5126, // (5126 = FLOAT) + attribute.normalized, + attribute.stride, + attribute.start, + attribute.instance); + } + + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; + + return vao; + } + + draw(type, size, start, instanceCount) + { + this._activeVao.draw(type, size, start, instanceCount); + } + + /** + * Creates a new VAO from this renderer's context and state. + * + * @return {VertexArrayObject} The new VAO. + */ + createVao() + { + return new glCore.VertexArrayObject(this.gl, this.renderer.state.attribState); + } + + /** + * Changes the current Vao to the one given in parameter + * + * @param {PIXI.VertexArrayObject} vao - the new Vao + * @return {PIXI.WebGLRenderer} Returns itself. + */ + bindVao(vao) + { + if (this._activeVao === vao) + { + return this; + } + + if (vao) + { + vao.bind(); + } + else if (this._activeVao) + { + // TODO this should always be true i think? + this._activeVao.unbind(); + } + + this._activeVao = vao; + + return this; + } +} diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/src/core/renderers/webgl/managers/FramebufferManager.js b/src/core/renderers/webgl/managers/FramebufferManager.js index 645fdc9..42c4556 100644 --- a/src/core/renderers/webgl/managers/FramebufferManager.js +++ b/src/core/renderers/webgl/managers/FramebufferManager.js @@ -20,7 +20,7 @@ * * @private */ - onContextChange() + contextChange() { this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; diff --git a/src/core/renderers/webgl/managers/GeometryManager.js b/src/core/renderers/webgl/managers/GeometryManager.js new file mode 100644 index 0000000..af3da72 --- /dev/null +++ b/src/core/renderers/webgl/managers/GeometryManager.js @@ -0,0 +1,216 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class GeometryManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeVao = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + */ + bind(geometry, glShader) + { + const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + + this.bindVao(vao); + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer._updateID) + { + glBuffer._updateID = buffer._updateID; + // TODO - partial upload?? + + glBuffer.upload(buffer.data, 0); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + * @return {PIXI.glCore.VertexArrayObject} Returns a fresh vao. + */ + initGeometryVao(geometry, glShader) + { + const gl = this.gl; + + this.bindVao(null); + + const vao = this.createVao(); + + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + // first update - and create the buffers! + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[this.CONTEXT_UID]) + { + if (buffer.index) + { + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createVertexBuffer(gl, buffer.data, buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); + } + } + } + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); + } + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const glAttribute = glShader.attributes[j]; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + } + } + + // next update the attributes buffer.. + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + // need to know the shader as it means we can be lazy and let pixi do the work for us.. + // stride, start, type? + vao.addAttribute(glBuffer, + glShader.attributes[j], + attribute.type || 5126, // (5126 = FLOAT) + attribute.normalized, + attribute.stride, + attribute.start, + attribute.instance); + } + + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; + + return vao; + } + + draw(type, size, start, instanceCount) + { + this._activeVao.draw(type, size, start, instanceCount); + } + + /** + * Creates a new VAO from this renderer's context and state. + * + * @return {VertexArrayObject} The new VAO. + */ + createVao() + { + return new glCore.VertexArrayObject(this.gl, this.renderer.state.attribState); + } + + /** + * Changes the current Vao to the one given in parameter + * + * @param {PIXI.VertexArrayObject} vao - the new Vao + * @return {PIXI.WebGLRenderer} Returns itself. + */ + bindVao(vao) + { + if (this._activeVao === vao) + { + return this; + } + + if (vao) + { + vao.bind(); + } + else if (this._activeVao) + { + // TODO this should always be true i think? + this._activeVao.unbind(); + } + + this._activeVao = vao; + + return this; + } +} diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index aad715a..1a2ec61 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -45,7 +45,7 @@ else if (this.enableScissor && !this.scissor && this.renderer._activeRenderTarget.root - && !this.renderer.stencilManager.stencilMaskStack.length + && !this.renderer.stencil.stencilMaskStack.length && maskData.isFastRect()) { const matrix = maskData.worldTransform; @@ -82,7 +82,7 @@ { this.popSpriteMask(target, maskData); } - else if (this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) { this.popScissorMask(target, maskData); } @@ -136,7 +136,7 @@ pushStencilMask(maskData) { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); + this.renderer.stencil.pushStencil(maskData); } /** @@ -146,7 +146,7 @@ popStencilMask() { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); + this.renderer.stencil.popStencil(); } /** diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/src/core/renderers/webgl/managers/FramebufferManager.js b/src/core/renderers/webgl/managers/FramebufferManager.js index 645fdc9..42c4556 100644 --- a/src/core/renderers/webgl/managers/FramebufferManager.js +++ b/src/core/renderers/webgl/managers/FramebufferManager.js @@ -20,7 +20,7 @@ * * @private */ - onContextChange() + contextChange() { this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; diff --git a/src/core/renderers/webgl/managers/GeometryManager.js b/src/core/renderers/webgl/managers/GeometryManager.js new file mode 100644 index 0000000..af3da72 --- /dev/null +++ b/src/core/renderers/webgl/managers/GeometryManager.js @@ -0,0 +1,216 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class GeometryManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeVao = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + */ + bind(geometry, glShader) + { + const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + + this.bindVao(vao); + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer._updateID) + { + glBuffer._updateID = buffer._updateID; + // TODO - partial upload?? + + glBuffer.upload(buffer.data, 0); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + * @return {PIXI.glCore.VertexArrayObject} Returns a fresh vao. + */ + initGeometryVao(geometry, glShader) + { + const gl = this.gl; + + this.bindVao(null); + + const vao = this.createVao(); + + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + // first update - and create the buffers! + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[this.CONTEXT_UID]) + { + if (buffer.index) + { + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createVertexBuffer(gl, buffer.data, buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); + } + } + } + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); + } + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const glAttribute = glShader.attributes[j]; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + } + } + + // next update the attributes buffer.. + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + // need to know the shader as it means we can be lazy and let pixi do the work for us.. + // stride, start, type? + vao.addAttribute(glBuffer, + glShader.attributes[j], + attribute.type || 5126, // (5126 = FLOAT) + attribute.normalized, + attribute.stride, + attribute.start, + attribute.instance); + } + + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; + + return vao; + } + + draw(type, size, start, instanceCount) + { + this._activeVao.draw(type, size, start, instanceCount); + } + + /** + * Creates a new VAO from this renderer's context and state. + * + * @return {VertexArrayObject} The new VAO. + */ + createVao() + { + return new glCore.VertexArrayObject(this.gl, this.renderer.state.attribState); + } + + /** + * Changes the current Vao to the one given in parameter + * + * @param {PIXI.VertexArrayObject} vao - the new Vao + * @return {PIXI.WebGLRenderer} Returns itself. + */ + bindVao(vao) + { + if (this._activeVao === vao) + { + return this; + } + + if (vao) + { + vao.bind(); + } + else if (this._activeVao) + { + // TODO this should always be true i think? + this._activeVao.unbind(); + } + + this._activeVao = vao; + + return this; + } +} diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index aad715a..1a2ec61 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -45,7 +45,7 @@ else if (this.enableScissor && !this.scissor && this.renderer._activeRenderTarget.root - && !this.renderer.stencilManager.stencilMaskStack.length + && !this.renderer.stencil.stencilMaskStack.length && maskData.isFastRect()) { const matrix = maskData.worldTransform; @@ -82,7 +82,7 @@ { this.popSpriteMask(target, maskData); } - else if (this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) { this.popScissorMask(target, maskData); } @@ -136,7 +136,7 @@ pushStencilMask(maskData) { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); + this.renderer.stencil.pushStencil(maskData); } /** @@ -146,7 +146,7 @@ popStencilMask() { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); + this.renderer.stencil.popStencil(); } /** diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index 02aee2c..51bcdf0 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -23,6 +23,14 @@ null, null, null, + null, + null, + null, + null, + null, + null, + null, + null, null ]; @@ -34,7 +42,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; @@ -54,6 +62,10 @@ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + for (var i = 0; i < this.boundTextures.length; i++) { + this.bind(null, i); + } } bind(texture, location) @@ -61,7 +73,6 @@ const gl = this.gl; - texture = texture.baseTexture || texture; location = location || 0; @@ -71,23 +82,29 @@ gl.activeTexture(gl.TEXTURE0 + location); } - if(texture && texture.valid) + if(texture) { - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + texture = texture.baseTexture || texture; - gl.bindTexture(texture.target, glTexture.texture); - - if(glTexture.dirtyId !== texture.dirtyId) + if(texture.valid) { - glTexture.dirtyId = texture.dirtyId; - this.updateTexture(texture); - } - this.boundTextures[location] = texture; + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + + gl.bindTexture(texture.target, glTexture.texture); + + if(glTexture.dirtyId !== texture.dirtyId) + { + glTexture.dirtyId = texture.dirtyId; + this.updateTexture(texture); + } + + this.boundTextures[location] = texture; + } } else { - gl.bindTexture(texture.target, this.emptyTextures[texture.target].texture); + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); this.boundTextures[location] = null; } } diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/src/core/renderers/webgl/managers/FramebufferManager.js b/src/core/renderers/webgl/managers/FramebufferManager.js index 645fdc9..42c4556 100644 --- a/src/core/renderers/webgl/managers/FramebufferManager.js +++ b/src/core/renderers/webgl/managers/FramebufferManager.js @@ -20,7 +20,7 @@ * * @private */ - onContextChange() + contextChange() { this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; diff --git a/src/core/renderers/webgl/managers/GeometryManager.js b/src/core/renderers/webgl/managers/GeometryManager.js new file mode 100644 index 0000000..af3da72 --- /dev/null +++ b/src/core/renderers/webgl/managers/GeometryManager.js @@ -0,0 +1,216 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class GeometryManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeVao = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + */ + bind(geometry, glShader) + { + const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + + this.bindVao(vao); + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer._updateID) + { + glBuffer._updateID = buffer._updateID; + // TODO - partial upload?? + + glBuffer.upload(buffer.data, 0); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + * @return {PIXI.glCore.VertexArrayObject} Returns a fresh vao. + */ + initGeometryVao(geometry, glShader) + { + const gl = this.gl; + + this.bindVao(null); + + const vao = this.createVao(); + + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + // first update - and create the buffers! + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[this.CONTEXT_UID]) + { + if (buffer.index) + { + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createVertexBuffer(gl, buffer.data, buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); + } + } + } + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); + } + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const glAttribute = glShader.attributes[j]; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + } + } + + // next update the attributes buffer.. + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + // need to know the shader as it means we can be lazy and let pixi do the work for us.. + // stride, start, type? + vao.addAttribute(glBuffer, + glShader.attributes[j], + attribute.type || 5126, // (5126 = FLOAT) + attribute.normalized, + attribute.stride, + attribute.start, + attribute.instance); + } + + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; + + return vao; + } + + draw(type, size, start, instanceCount) + { + this._activeVao.draw(type, size, start, instanceCount); + } + + /** + * Creates a new VAO from this renderer's context and state. + * + * @return {VertexArrayObject} The new VAO. + */ + createVao() + { + return new glCore.VertexArrayObject(this.gl, this.renderer.state.attribState); + } + + /** + * Changes the current Vao to the one given in parameter + * + * @param {PIXI.VertexArrayObject} vao - the new Vao + * @return {PIXI.WebGLRenderer} Returns itself. + */ + bindVao(vao) + { + if (this._activeVao === vao) + { + return this; + } + + if (vao) + { + vao.bind(); + } + else if (this._activeVao) + { + // TODO this should always be true i think? + this._activeVao.unbind(); + } + + this._activeVao = vao; + + return this; + } +} diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index aad715a..1a2ec61 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -45,7 +45,7 @@ else if (this.enableScissor && !this.scissor && this.renderer._activeRenderTarget.root - && !this.renderer.stencilManager.stencilMaskStack.length + && !this.renderer.stencil.stencilMaskStack.length && maskData.isFastRect()) { const matrix = maskData.worldTransform; @@ -82,7 +82,7 @@ { this.popSpriteMask(target, maskData); } - else if (this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) { this.popScissorMask(target, maskData); } @@ -136,7 +136,7 @@ pushStencilMask(maskData) { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); + this.renderer.stencil.pushStencil(maskData); } /** @@ -146,7 +146,7 @@ popStencilMask() { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); + this.renderer.stencil.popStencil(); } /** diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index 02aee2c..51bcdf0 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -23,6 +23,14 @@ null, null, null, + null, + null, + null, + null, + null, + null, + null, + null, null ]; @@ -34,7 +42,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; @@ -54,6 +62,10 @@ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + for (var i = 0; i < this.boundTextures.length; i++) { + this.bind(null, i); + } } bind(texture, location) @@ -61,7 +73,6 @@ const gl = this.gl; - texture = texture.baseTexture || texture; location = location || 0; @@ -71,23 +82,29 @@ gl.activeTexture(gl.TEXTURE0 + location); } - if(texture && texture.valid) + if(texture) { - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + texture = texture.baseTexture || texture; - gl.bindTexture(texture.target, glTexture.texture); - - if(glTexture.dirtyId !== texture.dirtyId) + if(texture.valid) { - glTexture.dirtyId = texture.dirtyId; - this.updateTexture(texture); - } - this.boundTextures[location] = texture; + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + + gl.bindTexture(texture.target, glTexture.texture); + + if(glTexture.dirtyId !== texture.dirtyId) + { + glTexture.dirtyId = texture.dirtyId; + this.updateTexture(texture); + } + + this.boundTextures[location] = texture; + } } else { - gl.bindTexture(texture.target, this.emptyTextures[texture.target].texture); + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); this.boundTextures[location] = null; } } diff --git a/src/core/renderers/webgl/managers/ProjectionManager.js b/src/core/renderers/webgl/managers/ProjectionManager.js index a9bea4b..752ac4a 100644 --- a/src/core/renderers/webgl/managers/ProjectionManager.js +++ b/src/core/renderers/webgl/managers/ProjectionManager.js @@ -89,4 +89,16 @@ pm.ty = 1 - (sourceFrame.y * pm.d); } } + + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform(matrix) + { + // this._activeRenderTarget.transform = matrix; + } + } diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/src/core/renderers/webgl/managers/FramebufferManager.js b/src/core/renderers/webgl/managers/FramebufferManager.js index 645fdc9..42c4556 100644 --- a/src/core/renderers/webgl/managers/FramebufferManager.js +++ b/src/core/renderers/webgl/managers/FramebufferManager.js @@ -20,7 +20,7 @@ * * @private */ - onContextChange() + contextChange() { this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; diff --git a/src/core/renderers/webgl/managers/GeometryManager.js b/src/core/renderers/webgl/managers/GeometryManager.js new file mode 100644 index 0000000..af3da72 --- /dev/null +++ b/src/core/renderers/webgl/managers/GeometryManager.js @@ -0,0 +1,216 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class GeometryManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeVao = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + */ + bind(geometry, glShader) + { + const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + + this.bindVao(vao); + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer._updateID) + { + glBuffer._updateID = buffer._updateID; + // TODO - partial upload?? + + glBuffer.upload(buffer.data, 0); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + * @return {PIXI.glCore.VertexArrayObject} Returns a fresh vao. + */ + initGeometryVao(geometry, glShader) + { + const gl = this.gl; + + this.bindVao(null); + + const vao = this.createVao(); + + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + // first update - and create the buffers! + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[this.CONTEXT_UID]) + { + if (buffer.index) + { + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createVertexBuffer(gl, buffer.data, buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); + } + } + } + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); + } + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const glAttribute = glShader.attributes[j]; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + } + } + + // next update the attributes buffer.. + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + // need to know the shader as it means we can be lazy and let pixi do the work for us.. + // stride, start, type? + vao.addAttribute(glBuffer, + glShader.attributes[j], + attribute.type || 5126, // (5126 = FLOAT) + attribute.normalized, + attribute.stride, + attribute.start, + attribute.instance); + } + + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; + + return vao; + } + + draw(type, size, start, instanceCount) + { + this._activeVao.draw(type, size, start, instanceCount); + } + + /** + * Creates a new VAO from this renderer's context and state. + * + * @return {VertexArrayObject} The new VAO. + */ + createVao() + { + return new glCore.VertexArrayObject(this.gl, this.renderer.state.attribState); + } + + /** + * Changes the current Vao to the one given in parameter + * + * @param {PIXI.VertexArrayObject} vao - the new Vao + * @return {PIXI.WebGLRenderer} Returns itself. + */ + bindVao(vao) + { + if (this._activeVao === vao) + { + return this; + } + + if (vao) + { + vao.bind(); + } + else if (this._activeVao) + { + // TODO this should always be true i think? + this._activeVao.unbind(); + } + + this._activeVao = vao; + + return this; + } +} diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index aad715a..1a2ec61 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -45,7 +45,7 @@ else if (this.enableScissor && !this.scissor && this.renderer._activeRenderTarget.root - && !this.renderer.stencilManager.stencilMaskStack.length + && !this.renderer.stencil.stencilMaskStack.length && maskData.isFastRect()) { const matrix = maskData.worldTransform; @@ -82,7 +82,7 @@ { this.popSpriteMask(target, maskData); } - else if (this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) { this.popScissorMask(target, maskData); } @@ -136,7 +136,7 @@ pushStencilMask(maskData) { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); + this.renderer.stencil.pushStencil(maskData); } /** @@ -146,7 +146,7 @@ popStencilMask() { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); + this.renderer.stencil.popStencil(); } /** diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index 02aee2c..51bcdf0 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -23,6 +23,14 @@ null, null, null, + null, + null, + null, + null, + null, + null, + null, + null, null ]; @@ -34,7 +42,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; @@ -54,6 +62,10 @@ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + for (var i = 0; i < this.boundTextures.length; i++) { + this.bind(null, i); + } } bind(texture, location) @@ -61,7 +73,6 @@ const gl = this.gl; - texture = texture.baseTexture || texture; location = location || 0; @@ -71,23 +82,29 @@ gl.activeTexture(gl.TEXTURE0 + location); } - if(texture && texture.valid) + if(texture) { - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + texture = texture.baseTexture || texture; - gl.bindTexture(texture.target, glTexture.texture); - - if(glTexture.dirtyId !== texture.dirtyId) + if(texture.valid) { - glTexture.dirtyId = texture.dirtyId; - this.updateTexture(texture); - } - this.boundTextures[location] = texture; + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + + gl.bindTexture(texture.target, glTexture.texture); + + if(glTexture.dirtyId !== texture.dirtyId) + { + glTexture.dirtyId = texture.dirtyId; + this.updateTexture(texture); + } + + this.boundTextures[location] = texture; + } } else { - gl.bindTexture(texture.target, this.emptyTextures[texture.target].texture); + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); this.boundTextures[location] = null; } } diff --git a/src/core/renderers/webgl/managers/ProjectionManager.js b/src/core/renderers/webgl/managers/ProjectionManager.js index a9bea4b..752ac4a 100644 --- a/src/core/renderers/webgl/managers/ProjectionManager.js +++ b/src/core/renderers/webgl/managers/ProjectionManager.js @@ -89,4 +89,16 @@ pm.ty = 1 - (sourceFrame.y * pm.d); } } + + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform(matrix) + { + // this._activeRenderTarget.transform = matrix; + } + } diff --git a/src/core/renderers/webgl/managers/ShaderManager.js b/src/core/renderers/webgl/managers/ShaderManager.js new file mode 100644 index 0000000..1c4f5f1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ShaderManager.js @@ -0,0 +1,165 @@ +import WebGLManager from './WebGLManager'; +import { GLShader } from 'pixi-gl-core'; +import { PRECISION } from '../../../const'; +import generateUniformsSync from '../../../shader/generateUniformsSync2'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI + */ +export default class ShaderManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. + */ + bind(shader, dontSync) + { + const program = shader.program; + const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + // if (this.shader !== shader) + // { + if (this.shader !== shader) + { + this.shader = shader; + glShader.bind(); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glShader; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); + } + + setUniformsGroups(uniformGroups) + { + + const glShader = this.getGLShader(); + + const group = uniformGroups[0]; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context + */ + getGLShader() + { + if(this.shader) + { + return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; + } + else + { + return null; + } + } + + /** + * Generates a GLShader verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glShader will be based on. + * @return {PIXI.glCore.GLShader} A shiney new GLShader + */ + generateShader(shader) + { + const program = shader.program; + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); + + program.glShaders[this.renderer.CONTEXT_UID] = glShader; + + return glShader; + } + + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderManager + this.destroyed = true; + } +} diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/src/core/renderers/webgl/managers/FramebufferManager.js b/src/core/renderers/webgl/managers/FramebufferManager.js index 645fdc9..42c4556 100644 --- a/src/core/renderers/webgl/managers/FramebufferManager.js +++ b/src/core/renderers/webgl/managers/FramebufferManager.js @@ -20,7 +20,7 @@ * * @private */ - onContextChange() + contextChange() { this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; diff --git a/src/core/renderers/webgl/managers/GeometryManager.js b/src/core/renderers/webgl/managers/GeometryManager.js new file mode 100644 index 0000000..af3da72 --- /dev/null +++ b/src/core/renderers/webgl/managers/GeometryManager.js @@ -0,0 +1,216 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class GeometryManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeVao = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + */ + bind(geometry, glShader) + { + const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + + this.bindVao(vao); + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer._updateID) + { + glBuffer._updateID = buffer._updateID; + // TODO - partial upload?? + + glBuffer.upload(buffer.data, 0); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + * @return {PIXI.glCore.VertexArrayObject} Returns a fresh vao. + */ + initGeometryVao(geometry, glShader) + { + const gl = this.gl; + + this.bindVao(null); + + const vao = this.createVao(); + + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + // first update - and create the buffers! + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[this.CONTEXT_UID]) + { + if (buffer.index) + { + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createVertexBuffer(gl, buffer.data, buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); + } + } + } + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); + } + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const glAttribute = glShader.attributes[j]; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + } + } + + // next update the attributes buffer.. + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + // need to know the shader as it means we can be lazy and let pixi do the work for us.. + // stride, start, type? + vao.addAttribute(glBuffer, + glShader.attributes[j], + attribute.type || 5126, // (5126 = FLOAT) + attribute.normalized, + attribute.stride, + attribute.start, + attribute.instance); + } + + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; + + return vao; + } + + draw(type, size, start, instanceCount) + { + this._activeVao.draw(type, size, start, instanceCount); + } + + /** + * Creates a new VAO from this renderer's context and state. + * + * @return {VertexArrayObject} The new VAO. + */ + createVao() + { + return new glCore.VertexArrayObject(this.gl, this.renderer.state.attribState); + } + + /** + * Changes the current Vao to the one given in parameter + * + * @param {PIXI.VertexArrayObject} vao - the new Vao + * @return {PIXI.WebGLRenderer} Returns itself. + */ + bindVao(vao) + { + if (this._activeVao === vao) + { + return this; + } + + if (vao) + { + vao.bind(); + } + else if (this._activeVao) + { + // TODO this should always be true i think? + this._activeVao.unbind(); + } + + this._activeVao = vao; + + return this; + } +} diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index aad715a..1a2ec61 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -45,7 +45,7 @@ else if (this.enableScissor && !this.scissor && this.renderer._activeRenderTarget.root - && !this.renderer.stencilManager.stencilMaskStack.length + && !this.renderer.stencil.stencilMaskStack.length && maskData.isFastRect()) { const matrix = maskData.worldTransform; @@ -82,7 +82,7 @@ { this.popSpriteMask(target, maskData); } - else if (this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) { this.popScissorMask(target, maskData); } @@ -136,7 +136,7 @@ pushStencilMask(maskData) { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); + this.renderer.stencil.pushStencil(maskData); } /** @@ -146,7 +146,7 @@ popStencilMask() { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); + this.renderer.stencil.popStencil(); } /** diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index 02aee2c..51bcdf0 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -23,6 +23,14 @@ null, null, null, + null, + null, + null, + null, + null, + null, + null, + null, null ]; @@ -34,7 +42,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; @@ -54,6 +62,10 @@ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + for (var i = 0; i < this.boundTextures.length; i++) { + this.bind(null, i); + } } bind(texture, location) @@ -61,7 +73,6 @@ const gl = this.gl; - texture = texture.baseTexture || texture; location = location || 0; @@ -71,23 +82,29 @@ gl.activeTexture(gl.TEXTURE0 + location); } - if(texture && texture.valid) + if(texture) { - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + texture = texture.baseTexture || texture; - gl.bindTexture(texture.target, glTexture.texture); - - if(glTexture.dirtyId !== texture.dirtyId) + if(texture.valid) { - glTexture.dirtyId = texture.dirtyId; - this.updateTexture(texture); - } - this.boundTextures[location] = texture; + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + + gl.bindTexture(texture.target, glTexture.texture); + + if(glTexture.dirtyId !== texture.dirtyId) + { + glTexture.dirtyId = texture.dirtyId; + this.updateTexture(texture); + } + + this.boundTextures[location] = texture; + } } else { - gl.bindTexture(texture.target, this.emptyTextures[texture.target].texture); + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); this.boundTextures[location] = null; } } diff --git a/src/core/renderers/webgl/managers/ProjectionManager.js b/src/core/renderers/webgl/managers/ProjectionManager.js index a9bea4b..752ac4a 100644 --- a/src/core/renderers/webgl/managers/ProjectionManager.js +++ b/src/core/renderers/webgl/managers/ProjectionManager.js @@ -89,4 +89,16 @@ pm.ty = 1 - (sourceFrame.y * pm.d); } } + + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform(matrix) + { + // this._activeRenderTarget.transform = matrix; + } + } diff --git a/src/core/renderers/webgl/managers/ShaderManager.js b/src/core/renderers/webgl/managers/ShaderManager.js new file mode 100644 index 0000000..1c4f5f1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ShaderManager.js @@ -0,0 +1,165 @@ +import WebGLManager from './WebGLManager'; +import { GLShader } from 'pixi-gl-core'; +import { PRECISION } from '../../../const'; +import generateUniformsSync from '../../../shader/generateUniformsSync2'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI + */ +export default class ShaderManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. + */ + bind(shader, dontSync) + { + const program = shader.program; + const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + // if (this.shader !== shader) + // { + if (this.shader !== shader) + { + this.shader = shader; + glShader.bind(); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glShader; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); + } + + setUniformsGroups(uniformGroups) + { + + const glShader = this.getGLShader(); + + const group = uniformGroups[0]; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context + */ + getGLShader() + { + if(this.shader) + { + return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; + } + else + { + return null; + } + } + + /** + * Generates a GLShader verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glShader will be based on. + * @return {PIXI.glCore.GLShader} A shiney new GLShader + */ + generateShader(shader) + { + const program = shader.program; + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); + + program.glShaders[this.renderer.CONTEXT_UID] = glShader; + + return glShader; + } + + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderManager + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/managers/StateManager.js b/src/core/renderers/webgl/managers/StateManager.js index 5f7f584..cc10a28 100755 --- a/src/core/renderers/webgl/managers/StateManager.js +++ b/src/core/renderers/webgl/managers/StateManager.js @@ -1,4 +1,5 @@ import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; +import WebGLManager from './WebGLManager'; import WebGLState from '../State'; const BLEND = 0; @@ -13,35 +14,23 @@ * @memberof PIXI * @class */ -export default class StateManager +export default class StateManager extends WebGLManager { /** * @param {WebGLRenderingContext} gl - The current WebGL rendering context */ - constructor(gl) + constructor(renderer) { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(renderer); - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + this.gl = null; - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); + this.maxAttribs = null; // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); + this.nativeVaoExtension = null; + + this.attribState = null; this.stateId = 0; this.polygonOffset = 0; @@ -62,7 +51,37 @@ this.defaultState.blend = true; this.defaultState.depth = true; + + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + this.setState(this.defaultState); + + this.reset(); } /** @@ -211,7 +230,7 @@ /** * Resets all the logic and disables the vaos */ - resetToDefault() + reset() { // unbind any VAO if they exist.. if (this.nativeVaoExtension) @@ -224,6 +243,8 @@ this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + this.setBlendMode(0); + // TODO? // this.setState(this.defaultState); } diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/src/core/renderers/webgl/managers/FramebufferManager.js b/src/core/renderers/webgl/managers/FramebufferManager.js index 645fdc9..42c4556 100644 --- a/src/core/renderers/webgl/managers/FramebufferManager.js +++ b/src/core/renderers/webgl/managers/FramebufferManager.js @@ -20,7 +20,7 @@ * * @private */ - onContextChange() + contextChange() { this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; diff --git a/src/core/renderers/webgl/managers/GeometryManager.js b/src/core/renderers/webgl/managers/GeometryManager.js new file mode 100644 index 0000000..af3da72 --- /dev/null +++ b/src/core/renderers/webgl/managers/GeometryManager.js @@ -0,0 +1,216 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class GeometryManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeVao = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + */ + bind(geometry, glShader) + { + const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + + this.bindVao(vao); + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer._updateID) + { + glBuffer._updateID = buffer._updateID; + // TODO - partial upload?? + + glBuffer.upload(buffer.data, 0); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + * @return {PIXI.glCore.VertexArrayObject} Returns a fresh vao. + */ + initGeometryVao(geometry, glShader) + { + const gl = this.gl; + + this.bindVao(null); + + const vao = this.createVao(); + + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + // first update - and create the buffers! + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[this.CONTEXT_UID]) + { + if (buffer.index) + { + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createVertexBuffer(gl, buffer.data, buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); + } + } + } + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); + } + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const glAttribute = glShader.attributes[j]; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + } + } + + // next update the attributes buffer.. + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + // need to know the shader as it means we can be lazy and let pixi do the work for us.. + // stride, start, type? + vao.addAttribute(glBuffer, + glShader.attributes[j], + attribute.type || 5126, // (5126 = FLOAT) + attribute.normalized, + attribute.stride, + attribute.start, + attribute.instance); + } + + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; + + return vao; + } + + draw(type, size, start, instanceCount) + { + this._activeVao.draw(type, size, start, instanceCount); + } + + /** + * Creates a new VAO from this renderer's context and state. + * + * @return {VertexArrayObject} The new VAO. + */ + createVao() + { + return new glCore.VertexArrayObject(this.gl, this.renderer.state.attribState); + } + + /** + * Changes the current Vao to the one given in parameter + * + * @param {PIXI.VertexArrayObject} vao - the new Vao + * @return {PIXI.WebGLRenderer} Returns itself. + */ + bindVao(vao) + { + if (this._activeVao === vao) + { + return this; + } + + if (vao) + { + vao.bind(); + } + else if (this._activeVao) + { + // TODO this should always be true i think? + this._activeVao.unbind(); + } + + this._activeVao = vao; + + return this; + } +} diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index aad715a..1a2ec61 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -45,7 +45,7 @@ else if (this.enableScissor && !this.scissor && this.renderer._activeRenderTarget.root - && !this.renderer.stencilManager.stencilMaskStack.length + && !this.renderer.stencil.stencilMaskStack.length && maskData.isFastRect()) { const matrix = maskData.worldTransform; @@ -82,7 +82,7 @@ { this.popSpriteMask(target, maskData); } - else if (this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) { this.popScissorMask(target, maskData); } @@ -136,7 +136,7 @@ pushStencilMask(maskData) { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); + this.renderer.stencil.pushStencil(maskData); } /** @@ -146,7 +146,7 @@ popStencilMask() { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); + this.renderer.stencil.popStencil(); } /** diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index 02aee2c..51bcdf0 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -23,6 +23,14 @@ null, null, null, + null, + null, + null, + null, + null, + null, + null, + null, null ]; @@ -34,7 +42,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; @@ -54,6 +62,10 @@ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + for (var i = 0; i < this.boundTextures.length; i++) { + this.bind(null, i); + } } bind(texture, location) @@ -61,7 +73,6 @@ const gl = this.gl; - texture = texture.baseTexture || texture; location = location || 0; @@ -71,23 +82,29 @@ gl.activeTexture(gl.TEXTURE0 + location); } - if(texture && texture.valid) + if(texture) { - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + texture = texture.baseTexture || texture; - gl.bindTexture(texture.target, glTexture.texture); - - if(glTexture.dirtyId !== texture.dirtyId) + if(texture.valid) { - glTexture.dirtyId = texture.dirtyId; - this.updateTexture(texture); - } - this.boundTextures[location] = texture; + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + + gl.bindTexture(texture.target, glTexture.texture); + + if(glTexture.dirtyId !== texture.dirtyId) + { + glTexture.dirtyId = texture.dirtyId; + this.updateTexture(texture); + } + + this.boundTextures[location] = texture; + } } else { - gl.bindTexture(texture.target, this.emptyTextures[texture.target].texture); + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); this.boundTextures[location] = null; } } diff --git a/src/core/renderers/webgl/managers/ProjectionManager.js b/src/core/renderers/webgl/managers/ProjectionManager.js index a9bea4b..752ac4a 100644 --- a/src/core/renderers/webgl/managers/ProjectionManager.js +++ b/src/core/renderers/webgl/managers/ProjectionManager.js @@ -89,4 +89,16 @@ pm.ty = 1 - (sourceFrame.y * pm.d); } } + + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform(matrix) + { + // this._activeRenderTarget.transform = matrix; + } + } diff --git a/src/core/renderers/webgl/managers/ShaderManager.js b/src/core/renderers/webgl/managers/ShaderManager.js new file mode 100644 index 0000000..1c4f5f1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ShaderManager.js @@ -0,0 +1,165 @@ +import WebGLManager from './WebGLManager'; +import { GLShader } from 'pixi-gl-core'; +import { PRECISION } from '../../../const'; +import generateUniformsSync from '../../../shader/generateUniformsSync2'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI + */ +export default class ShaderManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. + */ + bind(shader, dontSync) + { + const program = shader.program; + const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + // if (this.shader !== shader) + // { + if (this.shader !== shader) + { + this.shader = shader; + glShader.bind(); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glShader; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); + } + + setUniformsGroups(uniformGroups) + { + + const glShader = this.getGLShader(); + + const group = uniformGroups[0]; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context + */ + getGLShader() + { + if(this.shader) + { + return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; + } + else + { + return null; + } + } + + /** + * Generates a GLShader verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glShader will be based on. + * @return {PIXI.glCore.GLShader} A shiney new GLShader + */ + generateShader(shader) + { + const program = shader.program; + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); + + program.glShaders[this.renderer.CONTEXT_UID] = glShader; + + return glShader; + } + + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderManager + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/managers/StateManager.js b/src/core/renderers/webgl/managers/StateManager.js index 5f7f584..cc10a28 100755 --- a/src/core/renderers/webgl/managers/StateManager.js +++ b/src/core/renderers/webgl/managers/StateManager.js @@ -1,4 +1,5 @@ import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; +import WebGLManager from './WebGLManager'; import WebGLState from '../State'; const BLEND = 0; @@ -13,35 +14,23 @@ * @memberof PIXI * @class */ -export default class StateManager +export default class StateManager extends WebGLManager { /** * @param {WebGLRenderingContext} gl - The current WebGL rendering context */ - constructor(gl) + constructor(renderer) { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(renderer); - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + this.gl = null; - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); + this.maxAttribs = null; // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); + this.nativeVaoExtension = null; + + this.attribState = null; this.stateId = 0; this.polygonOffset = 0; @@ -62,7 +51,37 @@ this.defaultState.blend = true; this.defaultState.depth = true; + + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + this.setState(this.defaultState); + + this.reset(); } /** @@ -211,7 +230,7 @@ /** * Resets all the logic and disables the vaos */ - resetToDefault() + reset() { // unbind any VAO if they exist.. if (this.nativeVaoExtension) @@ -224,6 +243,8 @@ this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + this.setBlendMode(0); + // TODO? // this.setState(this.defaultState); } diff --git a/src/core/renderers/webgl/managers/TextureGCManager.js b/src/core/renderers/webgl/managers/TextureGCManager.js new file mode 100644 index 0000000..c7c93b3 --- /dev/null +++ b/src/core/renderers/webgl/managers/TextureGCManager.js @@ -0,0 +1,111 @@ +import WebGLManager from './WebGLManager'; +import { GC_MODES } from '../../../const'; +import settings from '../../../settings'; + +/** + * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged + * up with textures that are no longer being used. + * + * @class + * @memberof PIXI + */ +export default class TextureGCManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.count = 0; + this.checkCount = 0; + this.maxIdle = settings.GC_MAX_IDLE; + this.checkCountMax = settings.GC_MAX_CHECK_COUNT; + this.mode = settings.GC_MODE; + } + + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + postrender() + { + this.count++; + + if (this.mode === GC_MODES.MANUAL) + { + return; + } + + this.checkCount++; + + if (this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); + } + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() + { + const tm = this.renderer.textureManager; + const managedTextures = tm._managedTextures; + let wasRemoved = false; + + for (let i = 0; i < managedTextures.length; i++) + { + const texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + { + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; + } + } + + if (wasRemoved) + { + let j = 0; + + for (let i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } + } + + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. + */ + unload(displayObject) + { + const tm = this.renderer.textureManager; + + // only destroy non generated textures + if (displayObject._texture && displayObject._texture._glRenderTargets) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (let i = displayObject.children.length - 1; i >= 0; i--) + { + this.unload(displayObject.children[i]); + } + } +} diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/src/core/renderers/webgl/managers/FramebufferManager.js b/src/core/renderers/webgl/managers/FramebufferManager.js index 645fdc9..42c4556 100644 --- a/src/core/renderers/webgl/managers/FramebufferManager.js +++ b/src/core/renderers/webgl/managers/FramebufferManager.js @@ -20,7 +20,7 @@ * * @private */ - onContextChange() + contextChange() { this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; diff --git a/src/core/renderers/webgl/managers/GeometryManager.js b/src/core/renderers/webgl/managers/GeometryManager.js new file mode 100644 index 0000000..af3da72 --- /dev/null +++ b/src/core/renderers/webgl/managers/GeometryManager.js @@ -0,0 +1,216 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class GeometryManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeVao = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + */ + bind(geometry, glShader) + { + const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + + this.bindVao(vao); + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer._updateID) + { + glBuffer._updateID = buffer._updateID; + // TODO - partial upload?? + + glBuffer.upload(buffer.data, 0); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + * @return {PIXI.glCore.VertexArrayObject} Returns a fresh vao. + */ + initGeometryVao(geometry, glShader) + { + const gl = this.gl; + + this.bindVao(null); + + const vao = this.createVao(); + + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + // first update - and create the buffers! + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[this.CONTEXT_UID]) + { + if (buffer.index) + { + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createVertexBuffer(gl, buffer.data, buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); + } + } + } + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); + } + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const glAttribute = glShader.attributes[j]; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + } + } + + // next update the attributes buffer.. + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + // need to know the shader as it means we can be lazy and let pixi do the work for us.. + // stride, start, type? + vao.addAttribute(glBuffer, + glShader.attributes[j], + attribute.type || 5126, // (5126 = FLOAT) + attribute.normalized, + attribute.stride, + attribute.start, + attribute.instance); + } + + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; + + return vao; + } + + draw(type, size, start, instanceCount) + { + this._activeVao.draw(type, size, start, instanceCount); + } + + /** + * Creates a new VAO from this renderer's context and state. + * + * @return {VertexArrayObject} The new VAO. + */ + createVao() + { + return new glCore.VertexArrayObject(this.gl, this.renderer.state.attribState); + } + + /** + * Changes the current Vao to the one given in parameter + * + * @param {PIXI.VertexArrayObject} vao - the new Vao + * @return {PIXI.WebGLRenderer} Returns itself. + */ + bindVao(vao) + { + if (this._activeVao === vao) + { + return this; + } + + if (vao) + { + vao.bind(); + } + else if (this._activeVao) + { + // TODO this should always be true i think? + this._activeVao.unbind(); + } + + this._activeVao = vao; + + return this; + } +} diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index aad715a..1a2ec61 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -45,7 +45,7 @@ else if (this.enableScissor && !this.scissor && this.renderer._activeRenderTarget.root - && !this.renderer.stencilManager.stencilMaskStack.length + && !this.renderer.stencil.stencilMaskStack.length && maskData.isFastRect()) { const matrix = maskData.worldTransform; @@ -82,7 +82,7 @@ { this.popSpriteMask(target, maskData); } - else if (this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) { this.popScissorMask(target, maskData); } @@ -136,7 +136,7 @@ pushStencilMask(maskData) { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); + this.renderer.stencil.pushStencil(maskData); } /** @@ -146,7 +146,7 @@ popStencilMask() { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); + this.renderer.stencil.popStencil(); } /** diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index 02aee2c..51bcdf0 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -23,6 +23,14 @@ null, null, null, + null, + null, + null, + null, + null, + null, + null, + null, null ]; @@ -34,7 +42,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; @@ -54,6 +62,10 @@ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + for (var i = 0; i < this.boundTextures.length; i++) { + this.bind(null, i); + } } bind(texture, location) @@ -61,7 +73,6 @@ const gl = this.gl; - texture = texture.baseTexture || texture; location = location || 0; @@ -71,23 +82,29 @@ gl.activeTexture(gl.TEXTURE0 + location); } - if(texture && texture.valid) + if(texture) { - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + texture = texture.baseTexture || texture; - gl.bindTexture(texture.target, glTexture.texture); - - if(glTexture.dirtyId !== texture.dirtyId) + if(texture.valid) { - glTexture.dirtyId = texture.dirtyId; - this.updateTexture(texture); - } - this.boundTextures[location] = texture; + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + + gl.bindTexture(texture.target, glTexture.texture); + + if(glTexture.dirtyId !== texture.dirtyId) + { + glTexture.dirtyId = texture.dirtyId; + this.updateTexture(texture); + } + + this.boundTextures[location] = texture; + } } else { - gl.bindTexture(texture.target, this.emptyTextures[texture.target].texture); + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); this.boundTextures[location] = null; } } diff --git a/src/core/renderers/webgl/managers/ProjectionManager.js b/src/core/renderers/webgl/managers/ProjectionManager.js index a9bea4b..752ac4a 100644 --- a/src/core/renderers/webgl/managers/ProjectionManager.js +++ b/src/core/renderers/webgl/managers/ProjectionManager.js @@ -89,4 +89,16 @@ pm.ty = 1 - (sourceFrame.y * pm.d); } } + + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform(matrix) + { + // this._activeRenderTarget.transform = matrix; + } + } diff --git a/src/core/renderers/webgl/managers/ShaderManager.js b/src/core/renderers/webgl/managers/ShaderManager.js new file mode 100644 index 0000000..1c4f5f1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ShaderManager.js @@ -0,0 +1,165 @@ +import WebGLManager from './WebGLManager'; +import { GLShader } from 'pixi-gl-core'; +import { PRECISION } from '../../../const'; +import generateUniformsSync from '../../../shader/generateUniformsSync2'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI + */ +export default class ShaderManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. + */ + bind(shader, dontSync) + { + const program = shader.program; + const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + // if (this.shader !== shader) + // { + if (this.shader !== shader) + { + this.shader = shader; + glShader.bind(); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glShader; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); + } + + setUniformsGroups(uniformGroups) + { + + const glShader = this.getGLShader(); + + const group = uniformGroups[0]; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context + */ + getGLShader() + { + if(this.shader) + { + return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; + } + else + { + return null; + } + } + + /** + * Generates a GLShader verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glShader will be based on. + * @return {PIXI.glCore.GLShader} A shiney new GLShader + */ + generateShader(shader) + { + const program = shader.program; + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); + + program.glShaders[this.renderer.CONTEXT_UID] = glShader; + + return glShader; + } + + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderManager + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/managers/StateManager.js b/src/core/renderers/webgl/managers/StateManager.js index 5f7f584..cc10a28 100755 --- a/src/core/renderers/webgl/managers/StateManager.js +++ b/src/core/renderers/webgl/managers/StateManager.js @@ -1,4 +1,5 @@ import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; +import WebGLManager from './WebGLManager'; import WebGLState from '../State'; const BLEND = 0; @@ -13,35 +14,23 @@ * @memberof PIXI * @class */ -export default class StateManager +export default class StateManager extends WebGLManager { /** * @param {WebGLRenderingContext} gl - The current WebGL rendering context */ - constructor(gl) + constructor(renderer) { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(renderer); - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + this.gl = null; - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); + this.maxAttribs = null; // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); + this.nativeVaoExtension = null; + + this.attribState = null; this.stateId = 0; this.polygonOffset = 0; @@ -62,7 +51,37 @@ this.defaultState.blend = true; this.defaultState.depth = true; + + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + this.setState(this.defaultState); + + this.reset(); } /** @@ -211,7 +230,7 @@ /** * Resets all the logic and disables the vaos */ - resetToDefault() + reset() { // unbind any VAO if they exist.. if (this.nativeVaoExtension) @@ -224,6 +243,8 @@ this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + this.setBlendMode(0); + // TODO? // this.setState(this.defaultState); } diff --git a/src/core/renderers/webgl/managers/TextureGCManager.js b/src/core/renderers/webgl/managers/TextureGCManager.js new file mode 100644 index 0000000..c7c93b3 --- /dev/null +++ b/src/core/renderers/webgl/managers/TextureGCManager.js @@ -0,0 +1,111 @@ +import WebGLManager from './WebGLManager'; +import { GC_MODES } from '../../../const'; +import settings from '../../../settings'; + +/** + * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged + * up with textures that are no longer being used. + * + * @class + * @memberof PIXI + */ +export default class TextureGCManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.count = 0; + this.checkCount = 0; + this.maxIdle = settings.GC_MAX_IDLE; + this.checkCountMax = settings.GC_MAX_CHECK_COUNT; + this.mode = settings.GC_MODE; + } + + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + postrender() + { + this.count++; + + if (this.mode === GC_MODES.MANUAL) + { + return; + } + + this.checkCount++; + + if (this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); + } + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() + { + const tm = this.renderer.textureManager; + const managedTextures = tm._managedTextures; + let wasRemoved = false; + + for (let i = 0; i < managedTextures.length; i++) + { + const texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + { + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; + } + } + + if (wasRemoved) + { + let j = 0; + + for (let i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } + } + + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. + */ + unload(displayObject) + { + const tm = this.renderer.textureManager; + + // only destroy non generated textures + if (displayObject._texture && displayObject._texture._glRenderTargets) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (let i = displayObject.children.length - 1; i >= 0; i--) + { + this.unload(displayObject.children[i]); + } + } +} diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 62f97a8..59e4ba8 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -16,14 +16,14 @@ */ this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.runners.contextChange.add(this); } /** * Generic method called when there is a WebGL context change. * */ - onContextChange() + contextChange() { // do some codes init! } @@ -34,8 +34,7 @@ */ destroy() { - this.renderer.off('context', this.onContextChange, this); - + this.renderer.runners.contextChange.remove(this); this.renderer = null; } } diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/src/core/renderers/webgl/managers/FramebufferManager.js b/src/core/renderers/webgl/managers/FramebufferManager.js index 645fdc9..42c4556 100644 --- a/src/core/renderers/webgl/managers/FramebufferManager.js +++ b/src/core/renderers/webgl/managers/FramebufferManager.js @@ -20,7 +20,7 @@ * * @private */ - onContextChange() + contextChange() { this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; diff --git a/src/core/renderers/webgl/managers/GeometryManager.js b/src/core/renderers/webgl/managers/GeometryManager.js new file mode 100644 index 0000000..af3da72 --- /dev/null +++ b/src/core/renderers/webgl/managers/GeometryManager.js @@ -0,0 +1,216 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class GeometryManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeVao = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + */ + bind(geometry, glShader) + { + const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + + this.bindVao(vao); + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer._updateID) + { + glBuffer._updateID = buffer._updateID; + // TODO - partial upload?? + + glBuffer.upload(buffer.data, 0); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + * @return {PIXI.glCore.VertexArrayObject} Returns a fresh vao. + */ + initGeometryVao(geometry, glShader) + { + const gl = this.gl; + + this.bindVao(null); + + const vao = this.createVao(); + + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + // first update - and create the buffers! + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[this.CONTEXT_UID]) + { + if (buffer.index) + { + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createVertexBuffer(gl, buffer.data, buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); + } + } + } + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); + } + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const glAttribute = glShader.attributes[j]; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + } + } + + // next update the attributes buffer.. + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + // need to know the shader as it means we can be lazy and let pixi do the work for us.. + // stride, start, type? + vao.addAttribute(glBuffer, + glShader.attributes[j], + attribute.type || 5126, // (5126 = FLOAT) + attribute.normalized, + attribute.stride, + attribute.start, + attribute.instance); + } + + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; + + return vao; + } + + draw(type, size, start, instanceCount) + { + this._activeVao.draw(type, size, start, instanceCount); + } + + /** + * Creates a new VAO from this renderer's context and state. + * + * @return {VertexArrayObject} The new VAO. + */ + createVao() + { + return new glCore.VertexArrayObject(this.gl, this.renderer.state.attribState); + } + + /** + * Changes the current Vao to the one given in parameter + * + * @param {PIXI.VertexArrayObject} vao - the new Vao + * @return {PIXI.WebGLRenderer} Returns itself. + */ + bindVao(vao) + { + if (this._activeVao === vao) + { + return this; + } + + if (vao) + { + vao.bind(); + } + else if (this._activeVao) + { + // TODO this should always be true i think? + this._activeVao.unbind(); + } + + this._activeVao = vao; + + return this; + } +} diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index aad715a..1a2ec61 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -45,7 +45,7 @@ else if (this.enableScissor && !this.scissor && this.renderer._activeRenderTarget.root - && !this.renderer.stencilManager.stencilMaskStack.length + && !this.renderer.stencil.stencilMaskStack.length && maskData.isFastRect()) { const matrix = maskData.worldTransform; @@ -82,7 +82,7 @@ { this.popSpriteMask(target, maskData); } - else if (this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) { this.popScissorMask(target, maskData); } @@ -136,7 +136,7 @@ pushStencilMask(maskData) { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); + this.renderer.stencil.pushStencil(maskData); } /** @@ -146,7 +146,7 @@ popStencilMask() { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); + this.renderer.stencil.popStencil(); } /** diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index 02aee2c..51bcdf0 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -23,6 +23,14 @@ null, null, null, + null, + null, + null, + null, + null, + null, + null, + null, null ]; @@ -34,7 +42,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; @@ -54,6 +62,10 @@ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + for (var i = 0; i < this.boundTextures.length; i++) { + this.bind(null, i); + } } bind(texture, location) @@ -61,7 +73,6 @@ const gl = this.gl; - texture = texture.baseTexture || texture; location = location || 0; @@ -71,23 +82,29 @@ gl.activeTexture(gl.TEXTURE0 + location); } - if(texture && texture.valid) + if(texture) { - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + texture = texture.baseTexture || texture; - gl.bindTexture(texture.target, glTexture.texture); - - if(glTexture.dirtyId !== texture.dirtyId) + if(texture.valid) { - glTexture.dirtyId = texture.dirtyId; - this.updateTexture(texture); - } - this.boundTextures[location] = texture; + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + + gl.bindTexture(texture.target, glTexture.texture); + + if(glTexture.dirtyId !== texture.dirtyId) + { + glTexture.dirtyId = texture.dirtyId; + this.updateTexture(texture); + } + + this.boundTextures[location] = texture; + } } else { - gl.bindTexture(texture.target, this.emptyTextures[texture.target].texture); + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); this.boundTextures[location] = null; } } diff --git a/src/core/renderers/webgl/managers/ProjectionManager.js b/src/core/renderers/webgl/managers/ProjectionManager.js index a9bea4b..752ac4a 100644 --- a/src/core/renderers/webgl/managers/ProjectionManager.js +++ b/src/core/renderers/webgl/managers/ProjectionManager.js @@ -89,4 +89,16 @@ pm.ty = 1 - (sourceFrame.y * pm.d); } } + + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform(matrix) + { + // this._activeRenderTarget.transform = matrix; + } + } diff --git a/src/core/renderers/webgl/managers/ShaderManager.js b/src/core/renderers/webgl/managers/ShaderManager.js new file mode 100644 index 0000000..1c4f5f1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ShaderManager.js @@ -0,0 +1,165 @@ +import WebGLManager from './WebGLManager'; +import { GLShader } from 'pixi-gl-core'; +import { PRECISION } from '../../../const'; +import generateUniformsSync from '../../../shader/generateUniformsSync2'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI + */ +export default class ShaderManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. + */ + bind(shader, dontSync) + { + const program = shader.program; + const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + // if (this.shader !== shader) + // { + if (this.shader !== shader) + { + this.shader = shader; + glShader.bind(); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glShader; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); + } + + setUniformsGroups(uniformGroups) + { + + const glShader = this.getGLShader(); + + const group = uniformGroups[0]; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context + */ + getGLShader() + { + if(this.shader) + { + return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; + } + else + { + return null; + } + } + + /** + * Generates a GLShader verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glShader will be based on. + * @return {PIXI.glCore.GLShader} A shiney new GLShader + */ + generateShader(shader) + { + const program = shader.program; + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); + + program.glShaders[this.renderer.CONTEXT_UID] = glShader; + + return glShader; + } + + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderManager + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/managers/StateManager.js b/src/core/renderers/webgl/managers/StateManager.js index 5f7f584..cc10a28 100755 --- a/src/core/renderers/webgl/managers/StateManager.js +++ b/src/core/renderers/webgl/managers/StateManager.js @@ -1,4 +1,5 @@ import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; +import WebGLManager from './WebGLManager'; import WebGLState from '../State'; const BLEND = 0; @@ -13,35 +14,23 @@ * @memberof PIXI * @class */ -export default class StateManager +export default class StateManager extends WebGLManager { /** * @param {WebGLRenderingContext} gl - The current WebGL rendering context */ - constructor(gl) + constructor(renderer) { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(renderer); - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + this.gl = null; - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); + this.maxAttribs = null; // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); + this.nativeVaoExtension = null; + + this.attribState = null; this.stateId = 0; this.polygonOffset = 0; @@ -62,7 +51,37 @@ this.defaultState.blend = true; this.defaultState.depth = true; + + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + this.setState(this.defaultState); + + this.reset(); } /** @@ -211,7 +230,7 @@ /** * Resets all the logic and disables the vaos */ - resetToDefault() + reset() { // unbind any VAO if they exist.. if (this.nativeVaoExtension) @@ -224,6 +243,8 @@ this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + this.setBlendMode(0); + // TODO? // this.setState(this.defaultState); } diff --git a/src/core/renderers/webgl/managers/TextureGCManager.js b/src/core/renderers/webgl/managers/TextureGCManager.js new file mode 100644 index 0000000..c7c93b3 --- /dev/null +++ b/src/core/renderers/webgl/managers/TextureGCManager.js @@ -0,0 +1,111 @@ +import WebGLManager from './WebGLManager'; +import { GC_MODES } from '../../../const'; +import settings from '../../../settings'; + +/** + * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged + * up with textures that are no longer being used. + * + * @class + * @memberof PIXI + */ +export default class TextureGCManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.count = 0; + this.checkCount = 0; + this.maxIdle = settings.GC_MAX_IDLE; + this.checkCountMax = settings.GC_MAX_CHECK_COUNT; + this.mode = settings.GC_MODE; + } + + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + postrender() + { + this.count++; + + if (this.mode === GC_MODES.MANUAL) + { + return; + } + + this.checkCount++; + + if (this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); + } + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() + { + const tm = this.renderer.textureManager; + const managedTextures = tm._managedTextures; + let wasRemoved = false; + + for (let i = 0; i < managedTextures.length; i++) + { + const texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + { + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; + } + } + + if (wasRemoved) + { + let j = 0; + + for (let i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } + } + + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. + */ + unload(displayObject) + { + const tm = this.renderer.textureManager; + + // only destroy non generated textures + if (displayObject._texture && displayObject._texture._glRenderTargets) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (let i = displayObject.children.length - 1; i >= 0; i--) + { + this.unload(displayObject.children[i]); + } + } +} diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 62f97a8..59e4ba8 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -16,14 +16,14 @@ */ this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.runners.contextChange.add(this); } /** * Generic method called when there is a WebGL context change. * */ - onContextChange() + contextChange() { // do some codes init! } @@ -34,8 +34,7 @@ */ destroy() { - this.renderer.off('context', this.onContextChange, this); - + this.renderer.runners.contextChange.remove(this); this.renderer = null; } } diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 218fca5..f1f3571 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -1,26 +1,21 @@ import glCore from 'pixi-gl-core'; import createIndicesForQuads from '../../../utils/createIndicesForQuads'; - +import Geometry from '../../../geometry/Geometry' /** * Helper class to create a quad * * @class * @memberof PIXI */ -export default class Quad +export default class Quad extends Geometry { /** * @param {WebGLRenderingContext} gl - The gl context for this quad to use. * @param {object} state - TODO: Description */ - constructor(gl, state) + constructor() { - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(); /** * An array of vertices @@ -56,6 +51,7 @@ this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1]; } + /* * @member {Uint16Array} An array containing the indices of the vertices */ @@ -64,17 +60,21 @@ /* * @member {glCore.GLBuffer} The vertex buffer */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); /* * @member {glCore.GLBuffer} The index buffer */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); /* * @member {glCore.VertexArrayObject} The index buffer */ - this.vao = new glCore.VertexArrayObject(gl, state); + // this.vao = new glCore.VertexArrayObject(gl, state); + + this.addAttribute('aVertexPosition', this.vertices) + .addAttribute('aTextureCoord', this.uvs) + .addIndex(this.indices); } /** @@ -84,6 +84,7 @@ */ initVao(shader) { + return; this.vao.clear() .addIndex(this.indexBuffer) .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) @@ -139,6 +140,10 @@ */ upload() { + this.getAttribute('aVertexPosition').update(); + this.getAttribute('aTextureCoord').update(); + + return; for (let i = 0; i < 4; i++) { this.interleaved[i * 4] = this.vertices[(i * 2)]; diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/src/core/renderers/webgl/managers/FramebufferManager.js b/src/core/renderers/webgl/managers/FramebufferManager.js index 645fdc9..42c4556 100644 --- a/src/core/renderers/webgl/managers/FramebufferManager.js +++ b/src/core/renderers/webgl/managers/FramebufferManager.js @@ -20,7 +20,7 @@ * * @private */ - onContextChange() + contextChange() { this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; diff --git a/src/core/renderers/webgl/managers/GeometryManager.js b/src/core/renderers/webgl/managers/GeometryManager.js new file mode 100644 index 0000000..af3da72 --- /dev/null +++ b/src/core/renderers/webgl/managers/GeometryManager.js @@ -0,0 +1,216 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class GeometryManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeVao = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + */ + bind(geometry, glShader) + { + const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + + this.bindVao(vao); + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer._updateID) + { + glBuffer._updateID = buffer._updateID; + // TODO - partial upload?? + + glBuffer.upload(buffer.data, 0); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + * @return {PIXI.glCore.VertexArrayObject} Returns a fresh vao. + */ + initGeometryVao(geometry, glShader) + { + const gl = this.gl; + + this.bindVao(null); + + const vao = this.createVao(); + + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + // first update - and create the buffers! + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[this.CONTEXT_UID]) + { + if (buffer.index) + { + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createVertexBuffer(gl, buffer.data, buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); + } + } + } + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); + } + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const glAttribute = glShader.attributes[j]; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + } + } + + // next update the attributes buffer.. + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + // need to know the shader as it means we can be lazy and let pixi do the work for us.. + // stride, start, type? + vao.addAttribute(glBuffer, + glShader.attributes[j], + attribute.type || 5126, // (5126 = FLOAT) + attribute.normalized, + attribute.stride, + attribute.start, + attribute.instance); + } + + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; + + return vao; + } + + draw(type, size, start, instanceCount) + { + this._activeVao.draw(type, size, start, instanceCount); + } + + /** + * Creates a new VAO from this renderer's context and state. + * + * @return {VertexArrayObject} The new VAO. + */ + createVao() + { + return new glCore.VertexArrayObject(this.gl, this.renderer.state.attribState); + } + + /** + * Changes the current Vao to the one given in parameter + * + * @param {PIXI.VertexArrayObject} vao - the new Vao + * @return {PIXI.WebGLRenderer} Returns itself. + */ + bindVao(vao) + { + if (this._activeVao === vao) + { + return this; + } + + if (vao) + { + vao.bind(); + } + else if (this._activeVao) + { + // TODO this should always be true i think? + this._activeVao.unbind(); + } + + this._activeVao = vao; + + return this; + } +} diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index aad715a..1a2ec61 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -45,7 +45,7 @@ else if (this.enableScissor && !this.scissor && this.renderer._activeRenderTarget.root - && !this.renderer.stencilManager.stencilMaskStack.length + && !this.renderer.stencil.stencilMaskStack.length && maskData.isFastRect()) { const matrix = maskData.worldTransform; @@ -82,7 +82,7 @@ { this.popSpriteMask(target, maskData); } - else if (this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) { this.popScissorMask(target, maskData); } @@ -136,7 +136,7 @@ pushStencilMask(maskData) { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); + this.renderer.stencil.pushStencil(maskData); } /** @@ -146,7 +146,7 @@ popStencilMask() { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); + this.renderer.stencil.popStencil(); } /** diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index 02aee2c..51bcdf0 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -23,6 +23,14 @@ null, null, null, + null, + null, + null, + null, + null, + null, + null, + null, null ]; @@ -34,7 +42,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; @@ -54,6 +62,10 @@ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + for (var i = 0; i < this.boundTextures.length; i++) { + this.bind(null, i); + } } bind(texture, location) @@ -61,7 +73,6 @@ const gl = this.gl; - texture = texture.baseTexture || texture; location = location || 0; @@ -71,23 +82,29 @@ gl.activeTexture(gl.TEXTURE0 + location); } - if(texture && texture.valid) + if(texture) { - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + texture = texture.baseTexture || texture; - gl.bindTexture(texture.target, glTexture.texture); - - if(glTexture.dirtyId !== texture.dirtyId) + if(texture.valid) { - glTexture.dirtyId = texture.dirtyId; - this.updateTexture(texture); - } - this.boundTextures[location] = texture; + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + + gl.bindTexture(texture.target, glTexture.texture); + + if(glTexture.dirtyId !== texture.dirtyId) + { + glTexture.dirtyId = texture.dirtyId; + this.updateTexture(texture); + } + + this.boundTextures[location] = texture; + } } else { - gl.bindTexture(texture.target, this.emptyTextures[texture.target].texture); + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); this.boundTextures[location] = null; } } diff --git a/src/core/renderers/webgl/managers/ProjectionManager.js b/src/core/renderers/webgl/managers/ProjectionManager.js index a9bea4b..752ac4a 100644 --- a/src/core/renderers/webgl/managers/ProjectionManager.js +++ b/src/core/renderers/webgl/managers/ProjectionManager.js @@ -89,4 +89,16 @@ pm.ty = 1 - (sourceFrame.y * pm.d); } } + + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform(matrix) + { + // this._activeRenderTarget.transform = matrix; + } + } diff --git a/src/core/renderers/webgl/managers/ShaderManager.js b/src/core/renderers/webgl/managers/ShaderManager.js new file mode 100644 index 0000000..1c4f5f1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ShaderManager.js @@ -0,0 +1,165 @@ +import WebGLManager from './WebGLManager'; +import { GLShader } from 'pixi-gl-core'; +import { PRECISION } from '../../../const'; +import generateUniformsSync from '../../../shader/generateUniformsSync2'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI + */ +export default class ShaderManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. + */ + bind(shader, dontSync) + { + const program = shader.program; + const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + // if (this.shader !== shader) + // { + if (this.shader !== shader) + { + this.shader = shader; + glShader.bind(); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glShader; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); + } + + setUniformsGroups(uniformGroups) + { + + const glShader = this.getGLShader(); + + const group = uniformGroups[0]; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context + */ + getGLShader() + { + if(this.shader) + { + return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; + } + else + { + return null; + } + } + + /** + * Generates a GLShader verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glShader will be based on. + * @return {PIXI.glCore.GLShader} A shiney new GLShader + */ + generateShader(shader) + { + const program = shader.program; + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); + + program.glShaders[this.renderer.CONTEXT_UID] = glShader; + + return glShader; + } + + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderManager + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/managers/StateManager.js b/src/core/renderers/webgl/managers/StateManager.js index 5f7f584..cc10a28 100755 --- a/src/core/renderers/webgl/managers/StateManager.js +++ b/src/core/renderers/webgl/managers/StateManager.js @@ -1,4 +1,5 @@ import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; +import WebGLManager from './WebGLManager'; import WebGLState from '../State'; const BLEND = 0; @@ -13,35 +14,23 @@ * @memberof PIXI * @class */ -export default class StateManager +export default class StateManager extends WebGLManager { /** * @param {WebGLRenderingContext} gl - The current WebGL rendering context */ - constructor(gl) + constructor(renderer) { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(renderer); - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + this.gl = null; - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); + this.maxAttribs = null; // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); + this.nativeVaoExtension = null; + + this.attribState = null; this.stateId = 0; this.polygonOffset = 0; @@ -62,7 +51,37 @@ this.defaultState.blend = true; this.defaultState.depth = true; + + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + this.setState(this.defaultState); + + this.reset(); } /** @@ -211,7 +230,7 @@ /** * Resets all the logic and disables the vaos */ - resetToDefault() + reset() { // unbind any VAO if they exist.. if (this.nativeVaoExtension) @@ -224,6 +243,8 @@ this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + this.setBlendMode(0); + // TODO? // this.setState(this.defaultState); } diff --git a/src/core/renderers/webgl/managers/TextureGCManager.js b/src/core/renderers/webgl/managers/TextureGCManager.js new file mode 100644 index 0000000..c7c93b3 --- /dev/null +++ b/src/core/renderers/webgl/managers/TextureGCManager.js @@ -0,0 +1,111 @@ +import WebGLManager from './WebGLManager'; +import { GC_MODES } from '../../../const'; +import settings from '../../../settings'; + +/** + * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged + * up with textures that are no longer being used. + * + * @class + * @memberof PIXI + */ +export default class TextureGCManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.count = 0; + this.checkCount = 0; + this.maxIdle = settings.GC_MAX_IDLE; + this.checkCountMax = settings.GC_MAX_CHECK_COUNT; + this.mode = settings.GC_MODE; + } + + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + postrender() + { + this.count++; + + if (this.mode === GC_MODES.MANUAL) + { + return; + } + + this.checkCount++; + + if (this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); + } + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() + { + const tm = this.renderer.textureManager; + const managedTextures = tm._managedTextures; + let wasRemoved = false; + + for (let i = 0; i < managedTextures.length; i++) + { + const texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + { + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; + } + } + + if (wasRemoved) + { + let j = 0; + + for (let i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } + } + + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. + */ + unload(displayObject) + { + const tm = this.renderer.textureManager; + + // only destroy non generated textures + if (displayObject._texture && displayObject._texture._glRenderTargets) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (let i = displayObject.children.length - 1; i >= 0; i--) + { + this.unload(displayObject.children[i]); + } + } +} diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 62f97a8..59e4ba8 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -16,14 +16,14 @@ */ this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.runners.contextChange.add(this); } /** * Generic method called when there is a WebGL context change. * */ - onContextChange() + contextChange() { // do some codes init! } @@ -34,8 +34,7 @@ */ destroy() { - this.renderer.off('context', this.onContextChange, this); - + this.renderer.runners.contextChange.remove(this); this.renderer = null; } } diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 218fca5..f1f3571 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -1,26 +1,21 @@ import glCore from 'pixi-gl-core'; import createIndicesForQuads from '../../../utils/createIndicesForQuads'; - +import Geometry from '../../../geometry/Geometry' /** * Helper class to create a quad * * @class * @memberof PIXI */ -export default class Quad +export default class Quad extends Geometry { /** * @param {WebGLRenderingContext} gl - The gl context for this quad to use. * @param {object} state - TODO: Description */ - constructor(gl, state) + constructor() { - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(); /** * An array of vertices @@ -56,6 +51,7 @@ this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1]; } + /* * @member {Uint16Array} An array containing the indices of the vertices */ @@ -64,17 +60,21 @@ /* * @member {glCore.GLBuffer} The vertex buffer */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); /* * @member {glCore.GLBuffer} The index buffer */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); /* * @member {glCore.VertexArrayObject} The index buffer */ - this.vao = new glCore.VertexArrayObject(gl, state); + // this.vao = new glCore.VertexArrayObject(gl, state); + + this.addAttribute('aVertexPosition', this.vertices) + .addAttribute('aTextureCoord', this.uvs) + .addIndex(this.indices); } /** @@ -84,6 +84,7 @@ */ initVao(shader) { + return; this.vao.clear() .addIndex(this.indexBuffer) .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) @@ -139,6 +140,10 @@ */ upload() { + this.getAttribute('aVertexPosition').update(); + this.getAttribute('aTextureCoord').update(); + + return; for (let i = 0; i < 4; i++) { this.interleaved[i * 4] = this.vertices[(i * 2)]; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 8e757b1..e387e99 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -306,7 +306,7 @@ { this.calculateVertices(); - renderer.setObjectRenderer(renderer.plugins[this.pluginName]); + renderer.batch.setObjectRenderer(renderer.plugins[this.pluginName]); renderer.plugins[this.pluginName].render(this); } diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/src/core/renderers/webgl/managers/FramebufferManager.js b/src/core/renderers/webgl/managers/FramebufferManager.js index 645fdc9..42c4556 100644 --- a/src/core/renderers/webgl/managers/FramebufferManager.js +++ b/src/core/renderers/webgl/managers/FramebufferManager.js @@ -20,7 +20,7 @@ * * @private */ - onContextChange() + contextChange() { this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; diff --git a/src/core/renderers/webgl/managers/GeometryManager.js b/src/core/renderers/webgl/managers/GeometryManager.js new file mode 100644 index 0000000..af3da72 --- /dev/null +++ b/src/core/renderers/webgl/managers/GeometryManager.js @@ -0,0 +1,216 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class GeometryManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeVao = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + */ + bind(geometry, glShader) + { + const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + + this.bindVao(vao); + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer._updateID) + { + glBuffer._updateID = buffer._updateID; + // TODO - partial upload?? + + glBuffer.upload(buffer.data, 0); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + * @return {PIXI.glCore.VertexArrayObject} Returns a fresh vao. + */ + initGeometryVao(geometry, glShader) + { + const gl = this.gl; + + this.bindVao(null); + + const vao = this.createVao(); + + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + // first update - and create the buffers! + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[this.CONTEXT_UID]) + { + if (buffer.index) + { + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createVertexBuffer(gl, buffer.data, buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); + } + } + } + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); + } + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const glAttribute = glShader.attributes[j]; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + } + } + + // next update the attributes buffer.. + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + // need to know the shader as it means we can be lazy and let pixi do the work for us.. + // stride, start, type? + vao.addAttribute(glBuffer, + glShader.attributes[j], + attribute.type || 5126, // (5126 = FLOAT) + attribute.normalized, + attribute.stride, + attribute.start, + attribute.instance); + } + + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; + + return vao; + } + + draw(type, size, start, instanceCount) + { + this._activeVao.draw(type, size, start, instanceCount); + } + + /** + * Creates a new VAO from this renderer's context and state. + * + * @return {VertexArrayObject} The new VAO. + */ + createVao() + { + return new glCore.VertexArrayObject(this.gl, this.renderer.state.attribState); + } + + /** + * Changes the current Vao to the one given in parameter + * + * @param {PIXI.VertexArrayObject} vao - the new Vao + * @return {PIXI.WebGLRenderer} Returns itself. + */ + bindVao(vao) + { + if (this._activeVao === vao) + { + return this; + } + + if (vao) + { + vao.bind(); + } + else if (this._activeVao) + { + // TODO this should always be true i think? + this._activeVao.unbind(); + } + + this._activeVao = vao; + + return this; + } +} diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index aad715a..1a2ec61 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -45,7 +45,7 @@ else if (this.enableScissor && !this.scissor && this.renderer._activeRenderTarget.root - && !this.renderer.stencilManager.stencilMaskStack.length + && !this.renderer.stencil.stencilMaskStack.length && maskData.isFastRect()) { const matrix = maskData.worldTransform; @@ -82,7 +82,7 @@ { this.popSpriteMask(target, maskData); } - else if (this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) { this.popScissorMask(target, maskData); } @@ -136,7 +136,7 @@ pushStencilMask(maskData) { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); + this.renderer.stencil.pushStencil(maskData); } /** @@ -146,7 +146,7 @@ popStencilMask() { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); + this.renderer.stencil.popStencil(); } /** diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index 02aee2c..51bcdf0 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -23,6 +23,14 @@ null, null, null, + null, + null, + null, + null, + null, + null, + null, + null, null ]; @@ -34,7 +42,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; @@ -54,6 +62,10 @@ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + for (var i = 0; i < this.boundTextures.length; i++) { + this.bind(null, i); + } } bind(texture, location) @@ -61,7 +73,6 @@ const gl = this.gl; - texture = texture.baseTexture || texture; location = location || 0; @@ -71,23 +82,29 @@ gl.activeTexture(gl.TEXTURE0 + location); } - if(texture && texture.valid) + if(texture) { - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + texture = texture.baseTexture || texture; - gl.bindTexture(texture.target, glTexture.texture); - - if(glTexture.dirtyId !== texture.dirtyId) + if(texture.valid) { - glTexture.dirtyId = texture.dirtyId; - this.updateTexture(texture); - } - this.boundTextures[location] = texture; + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + + gl.bindTexture(texture.target, glTexture.texture); + + if(glTexture.dirtyId !== texture.dirtyId) + { + glTexture.dirtyId = texture.dirtyId; + this.updateTexture(texture); + } + + this.boundTextures[location] = texture; + } } else { - gl.bindTexture(texture.target, this.emptyTextures[texture.target].texture); + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); this.boundTextures[location] = null; } } diff --git a/src/core/renderers/webgl/managers/ProjectionManager.js b/src/core/renderers/webgl/managers/ProjectionManager.js index a9bea4b..752ac4a 100644 --- a/src/core/renderers/webgl/managers/ProjectionManager.js +++ b/src/core/renderers/webgl/managers/ProjectionManager.js @@ -89,4 +89,16 @@ pm.ty = 1 - (sourceFrame.y * pm.d); } } + + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform(matrix) + { + // this._activeRenderTarget.transform = matrix; + } + } diff --git a/src/core/renderers/webgl/managers/ShaderManager.js b/src/core/renderers/webgl/managers/ShaderManager.js new file mode 100644 index 0000000..1c4f5f1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ShaderManager.js @@ -0,0 +1,165 @@ +import WebGLManager from './WebGLManager'; +import { GLShader } from 'pixi-gl-core'; +import { PRECISION } from '../../../const'; +import generateUniformsSync from '../../../shader/generateUniformsSync2'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI + */ +export default class ShaderManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. + */ + bind(shader, dontSync) + { + const program = shader.program; + const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + // if (this.shader !== shader) + // { + if (this.shader !== shader) + { + this.shader = shader; + glShader.bind(); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glShader; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); + } + + setUniformsGroups(uniformGroups) + { + + const glShader = this.getGLShader(); + + const group = uniformGroups[0]; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context + */ + getGLShader() + { + if(this.shader) + { + return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; + } + else + { + return null; + } + } + + /** + * Generates a GLShader verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glShader will be based on. + * @return {PIXI.glCore.GLShader} A shiney new GLShader + */ + generateShader(shader) + { + const program = shader.program; + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); + + program.glShaders[this.renderer.CONTEXT_UID] = glShader; + + return glShader; + } + + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderManager + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/managers/StateManager.js b/src/core/renderers/webgl/managers/StateManager.js index 5f7f584..cc10a28 100755 --- a/src/core/renderers/webgl/managers/StateManager.js +++ b/src/core/renderers/webgl/managers/StateManager.js @@ -1,4 +1,5 @@ import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; +import WebGLManager from './WebGLManager'; import WebGLState from '../State'; const BLEND = 0; @@ -13,35 +14,23 @@ * @memberof PIXI * @class */ -export default class StateManager +export default class StateManager extends WebGLManager { /** * @param {WebGLRenderingContext} gl - The current WebGL rendering context */ - constructor(gl) + constructor(renderer) { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(renderer); - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + this.gl = null; - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); + this.maxAttribs = null; // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); + this.nativeVaoExtension = null; + + this.attribState = null; this.stateId = 0; this.polygonOffset = 0; @@ -62,7 +51,37 @@ this.defaultState.blend = true; this.defaultState.depth = true; + + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + this.setState(this.defaultState); + + this.reset(); } /** @@ -211,7 +230,7 @@ /** * Resets all the logic and disables the vaos */ - resetToDefault() + reset() { // unbind any VAO if they exist.. if (this.nativeVaoExtension) @@ -224,6 +243,8 @@ this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + this.setBlendMode(0); + // TODO? // this.setState(this.defaultState); } diff --git a/src/core/renderers/webgl/managers/TextureGCManager.js b/src/core/renderers/webgl/managers/TextureGCManager.js new file mode 100644 index 0000000..c7c93b3 --- /dev/null +++ b/src/core/renderers/webgl/managers/TextureGCManager.js @@ -0,0 +1,111 @@ +import WebGLManager from './WebGLManager'; +import { GC_MODES } from '../../../const'; +import settings from '../../../settings'; + +/** + * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged + * up with textures that are no longer being used. + * + * @class + * @memberof PIXI + */ +export default class TextureGCManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.count = 0; + this.checkCount = 0; + this.maxIdle = settings.GC_MAX_IDLE; + this.checkCountMax = settings.GC_MAX_CHECK_COUNT; + this.mode = settings.GC_MODE; + } + + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + postrender() + { + this.count++; + + if (this.mode === GC_MODES.MANUAL) + { + return; + } + + this.checkCount++; + + if (this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); + } + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() + { + const tm = this.renderer.textureManager; + const managedTextures = tm._managedTextures; + let wasRemoved = false; + + for (let i = 0; i < managedTextures.length; i++) + { + const texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + { + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; + } + } + + if (wasRemoved) + { + let j = 0; + + for (let i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } + } + + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. + */ + unload(displayObject) + { + const tm = this.renderer.textureManager; + + // only destroy non generated textures + if (displayObject._texture && displayObject._texture._glRenderTargets) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (let i = displayObject.children.length - 1; i >= 0; i--) + { + this.unload(displayObject.children[i]); + } + } +} diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 62f97a8..59e4ba8 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -16,14 +16,14 @@ */ this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.runners.contextChange.add(this); } /** * Generic method called when there is a WebGL context change. * */ - onContextChange() + contextChange() { // do some codes init! } @@ -34,8 +34,7 @@ */ destroy() { - this.renderer.off('context', this.onContextChange, this); - + this.renderer.runners.contextChange.remove(this); this.renderer = null; } } diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 218fca5..f1f3571 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -1,26 +1,21 @@ import glCore from 'pixi-gl-core'; import createIndicesForQuads from '../../../utils/createIndicesForQuads'; - +import Geometry from '../../../geometry/Geometry' /** * Helper class to create a quad * * @class * @memberof PIXI */ -export default class Quad +export default class Quad extends Geometry { /** * @param {WebGLRenderingContext} gl - The gl context for this quad to use. * @param {object} state - TODO: Description */ - constructor(gl, state) + constructor() { - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(); /** * An array of vertices @@ -56,6 +51,7 @@ this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1]; } + /* * @member {Uint16Array} An array containing the indices of the vertices */ @@ -64,17 +60,21 @@ /* * @member {glCore.GLBuffer} The vertex buffer */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); /* * @member {glCore.GLBuffer} The index buffer */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); /* * @member {glCore.VertexArrayObject} The index buffer */ - this.vao = new glCore.VertexArrayObject(gl, state); + // this.vao = new glCore.VertexArrayObject(gl, state); + + this.addAttribute('aVertexPosition', this.vertices) + .addAttribute('aTextureCoord', this.uvs) + .addIndex(this.indices); } /** @@ -84,6 +84,7 @@ */ initVao(shader) { + return; this.vao.clear() .addIndex(this.indexBuffer) .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) @@ -139,6 +140,10 @@ */ upload() { + this.getAttribute('aVertexPosition').update(); + this.getAttribute('aTextureCoord').update(); + + return; for (let i = 0; i < 4; i++) { this.interleaved[i * 4] = this.vertices[(i * 2)]; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 8e757b1..e387e99 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -306,7 +306,7 @@ { this.calculateVertices(); - renderer.setObjectRenderer(renderer.plugins[this.pluginName]); + renderer.batch.setObjectRenderer(renderer.plugins[this.pluginName]); renderer.plugins[this.pluginName].render(this); } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7952fb3..fe7f597 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -98,7 +98,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.renderer.gl; @@ -133,7 +133,7 @@ // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. - this.renderer.bindVao(null); + this.renderer.geometry.bindVao(null); for (let i = 0; i < this.vaoMax; i++) { @@ -142,7 +142,7 @@ var attributeData = shader.program.attributeData; // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() + this.vaos[i] = this.renderer.geometry.createVao() .addIndex(this.indexBuffer) .addAttribute(this.vertexBuffers[i], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) .addAttribute(this.vertexBuffers[i], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) @@ -410,7 +410,7 @@ /* eslint-enable max-len */ } - this.renderer.bindVao(this.vaos[this.vertexCount]); + this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false); @@ -464,13 +464,13 @@ start() { // this.renderer._bindGLShader(this.shader); - this.renderer.shader.bindShader(this.shader, true); + this.renderer.shader.bind(this.shader, true); this.renderer.shader.syncUniformGroup(this.shader.uniformGroup); if (settings.CAN_UPLOAD_SAME_BUFFER) { // bind buffer #0, we don't need others - this.renderer.bindVao(this.vaos[this.vertexCount]); + this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); this.vertexBuffers[this.vertexCount].bind(); } diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/src/core/renderers/webgl/managers/FramebufferManager.js b/src/core/renderers/webgl/managers/FramebufferManager.js index 645fdc9..42c4556 100644 --- a/src/core/renderers/webgl/managers/FramebufferManager.js +++ b/src/core/renderers/webgl/managers/FramebufferManager.js @@ -20,7 +20,7 @@ * * @private */ - onContextChange() + contextChange() { this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; diff --git a/src/core/renderers/webgl/managers/GeometryManager.js b/src/core/renderers/webgl/managers/GeometryManager.js new file mode 100644 index 0000000..af3da72 --- /dev/null +++ b/src/core/renderers/webgl/managers/GeometryManager.js @@ -0,0 +1,216 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class GeometryManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeVao = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + */ + bind(geometry, glShader) + { + const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + + this.bindVao(vao); + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer._updateID) + { + glBuffer._updateID = buffer._updateID; + // TODO - partial upload?? + + glBuffer.upload(buffer.data, 0); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + * @return {PIXI.glCore.VertexArrayObject} Returns a fresh vao. + */ + initGeometryVao(geometry, glShader) + { + const gl = this.gl; + + this.bindVao(null); + + const vao = this.createVao(); + + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + // first update - and create the buffers! + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[this.CONTEXT_UID]) + { + if (buffer.index) + { + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createVertexBuffer(gl, buffer.data, buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); + } + } + } + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); + } + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const glAttribute = glShader.attributes[j]; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + } + } + + // next update the attributes buffer.. + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + // need to know the shader as it means we can be lazy and let pixi do the work for us.. + // stride, start, type? + vao.addAttribute(glBuffer, + glShader.attributes[j], + attribute.type || 5126, // (5126 = FLOAT) + attribute.normalized, + attribute.stride, + attribute.start, + attribute.instance); + } + + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; + + return vao; + } + + draw(type, size, start, instanceCount) + { + this._activeVao.draw(type, size, start, instanceCount); + } + + /** + * Creates a new VAO from this renderer's context and state. + * + * @return {VertexArrayObject} The new VAO. + */ + createVao() + { + return new glCore.VertexArrayObject(this.gl, this.renderer.state.attribState); + } + + /** + * Changes the current Vao to the one given in parameter + * + * @param {PIXI.VertexArrayObject} vao - the new Vao + * @return {PIXI.WebGLRenderer} Returns itself. + */ + bindVao(vao) + { + if (this._activeVao === vao) + { + return this; + } + + if (vao) + { + vao.bind(); + } + else if (this._activeVao) + { + // TODO this should always be true i think? + this._activeVao.unbind(); + } + + this._activeVao = vao; + + return this; + } +} diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index aad715a..1a2ec61 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -45,7 +45,7 @@ else if (this.enableScissor && !this.scissor && this.renderer._activeRenderTarget.root - && !this.renderer.stencilManager.stencilMaskStack.length + && !this.renderer.stencil.stencilMaskStack.length && maskData.isFastRect()) { const matrix = maskData.worldTransform; @@ -82,7 +82,7 @@ { this.popSpriteMask(target, maskData); } - else if (this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) { this.popScissorMask(target, maskData); } @@ -136,7 +136,7 @@ pushStencilMask(maskData) { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); + this.renderer.stencil.pushStencil(maskData); } /** @@ -146,7 +146,7 @@ popStencilMask() { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); + this.renderer.stencil.popStencil(); } /** diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index 02aee2c..51bcdf0 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -23,6 +23,14 @@ null, null, null, + null, + null, + null, + null, + null, + null, + null, + null, null ]; @@ -34,7 +42,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; @@ -54,6 +62,10 @@ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + for (var i = 0; i < this.boundTextures.length; i++) { + this.bind(null, i); + } } bind(texture, location) @@ -61,7 +73,6 @@ const gl = this.gl; - texture = texture.baseTexture || texture; location = location || 0; @@ -71,23 +82,29 @@ gl.activeTexture(gl.TEXTURE0 + location); } - if(texture && texture.valid) + if(texture) { - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + texture = texture.baseTexture || texture; - gl.bindTexture(texture.target, glTexture.texture); - - if(glTexture.dirtyId !== texture.dirtyId) + if(texture.valid) { - glTexture.dirtyId = texture.dirtyId; - this.updateTexture(texture); - } - this.boundTextures[location] = texture; + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + + gl.bindTexture(texture.target, glTexture.texture); + + if(glTexture.dirtyId !== texture.dirtyId) + { + glTexture.dirtyId = texture.dirtyId; + this.updateTexture(texture); + } + + this.boundTextures[location] = texture; + } } else { - gl.bindTexture(texture.target, this.emptyTextures[texture.target].texture); + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); this.boundTextures[location] = null; } } diff --git a/src/core/renderers/webgl/managers/ProjectionManager.js b/src/core/renderers/webgl/managers/ProjectionManager.js index a9bea4b..752ac4a 100644 --- a/src/core/renderers/webgl/managers/ProjectionManager.js +++ b/src/core/renderers/webgl/managers/ProjectionManager.js @@ -89,4 +89,16 @@ pm.ty = 1 - (sourceFrame.y * pm.d); } } + + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform(matrix) + { + // this._activeRenderTarget.transform = matrix; + } + } diff --git a/src/core/renderers/webgl/managers/ShaderManager.js b/src/core/renderers/webgl/managers/ShaderManager.js new file mode 100644 index 0000000..1c4f5f1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ShaderManager.js @@ -0,0 +1,165 @@ +import WebGLManager from './WebGLManager'; +import { GLShader } from 'pixi-gl-core'; +import { PRECISION } from '../../../const'; +import generateUniformsSync from '../../../shader/generateUniformsSync2'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI + */ +export default class ShaderManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. + */ + bind(shader, dontSync) + { + const program = shader.program; + const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + // if (this.shader !== shader) + // { + if (this.shader !== shader) + { + this.shader = shader; + glShader.bind(); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glShader; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); + } + + setUniformsGroups(uniformGroups) + { + + const glShader = this.getGLShader(); + + const group = uniformGroups[0]; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context + */ + getGLShader() + { + if(this.shader) + { + return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; + } + else + { + return null; + } + } + + /** + * Generates a GLShader verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glShader will be based on. + * @return {PIXI.glCore.GLShader} A shiney new GLShader + */ + generateShader(shader) + { + const program = shader.program; + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); + + program.glShaders[this.renderer.CONTEXT_UID] = glShader; + + return glShader; + } + + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderManager + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/managers/StateManager.js b/src/core/renderers/webgl/managers/StateManager.js index 5f7f584..cc10a28 100755 --- a/src/core/renderers/webgl/managers/StateManager.js +++ b/src/core/renderers/webgl/managers/StateManager.js @@ -1,4 +1,5 @@ import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; +import WebGLManager from './WebGLManager'; import WebGLState from '../State'; const BLEND = 0; @@ -13,35 +14,23 @@ * @memberof PIXI * @class */ -export default class StateManager +export default class StateManager extends WebGLManager { /** * @param {WebGLRenderingContext} gl - The current WebGL rendering context */ - constructor(gl) + constructor(renderer) { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(renderer); - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + this.gl = null; - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); + this.maxAttribs = null; // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); + this.nativeVaoExtension = null; + + this.attribState = null; this.stateId = 0; this.polygonOffset = 0; @@ -62,7 +51,37 @@ this.defaultState.blend = true; this.defaultState.depth = true; + + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + this.setState(this.defaultState); + + this.reset(); } /** @@ -211,7 +230,7 @@ /** * Resets all the logic and disables the vaos */ - resetToDefault() + reset() { // unbind any VAO if they exist.. if (this.nativeVaoExtension) @@ -224,6 +243,8 @@ this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + this.setBlendMode(0); + // TODO? // this.setState(this.defaultState); } diff --git a/src/core/renderers/webgl/managers/TextureGCManager.js b/src/core/renderers/webgl/managers/TextureGCManager.js new file mode 100644 index 0000000..c7c93b3 --- /dev/null +++ b/src/core/renderers/webgl/managers/TextureGCManager.js @@ -0,0 +1,111 @@ +import WebGLManager from './WebGLManager'; +import { GC_MODES } from '../../../const'; +import settings from '../../../settings'; + +/** + * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged + * up with textures that are no longer being used. + * + * @class + * @memberof PIXI + */ +export default class TextureGCManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.count = 0; + this.checkCount = 0; + this.maxIdle = settings.GC_MAX_IDLE; + this.checkCountMax = settings.GC_MAX_CHECK_COUNT; + this.mode = settings.GC_MODE; + } + + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + postrender() + { + this.count++; + + if (this.mode === GC_MODES.MANUAL) + { + return; + } + + this.checkCount++; + + if (this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); + } + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() + { + const tm = this.renderer.textureManager; + const managedTextures = tm._managedTextures; + let wasRemoved = false; + + for (let i = 0; i < managedTextures.length; i++) + { + const texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + { + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; + } + } + + if (wasRemoved) + { + let j = 0; + + for (let i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } + } + + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. + */ + unload(displayObject) + { + const tm = this.renderer.textureManager; + + // only destroy non generated textures + if (displayObject._texture && displayObject._texture._glRenderTargets) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (let i = displayObject.children.length - 1; i >= 0; i--) + { + this.unload(displayObject.children[i]); + } + } +} diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 62f97a8..59e4ba8 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -16,14 +16,14 @@ */ this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.runners.contextChange.add(this); } /** * Generic method called when there is a WebGL context change. * */ - onContextChange() + contextChange() { // do some codes init! } @@ -34,8 +34,7 @@ */ destroy() { - this.renderer.off('context', this.onContextChange, this); - + this.renderer.runners.contextChange.remove(this); this.renderer = null; } } diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 218fca5..f1f3571 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -1,26 +1,21 @@ import glCore from 'pixi-gl-core'; import createIndicesForQuads from '../../../utils/createIndicesForQuads'; - +import Geometry from '../../../geometry/Geometry' /** * Helper class to create a quad * * @class * @memberof PIXI */ -export default class Quad +export default class Quad extends Geometry { /** * @param {WebGLRenderingContext} gl - The gl context for this quad to use. * @param {object} state - TODO: Description */ - constructor(gl, state) + constructor() { - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(); /** * An array of vertices @@ -56,6 +51,7 @@ this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1]; } + /* * @member {Uint16Array} An array containing the indices of the vertices */ @@ -64,17 +60,21 @@ /* * @member {glCore.GLBuffer} The vertex buffer */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); /* * @member {glCore.GLBuffer} The index buffer */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); /* * @member {glCore.VertexArrayObject} The index buffer */ - this.vao = new glCore.VertexArrayObject(gl, state); + // this.vao = new glCore.VertexArrayObject(gl, state); + + this.addAttribute('aVertexPosition', this.vertices) + .addAttribute('aTextureCoord', this.uvs) + .addIndex(this.indices); } /** @@ -84,6 +84,7 @@ */ initVao(shader) { + return; this.vao.clear() .addIndex(this.indexBuffer) .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) @@ -139,6 +140,10 @@ */ upload() { + this.getAttribute('aVertexPosition').update(); + this.getAttribute('aTextureCoord').update(); + + return; for (let i = 0; i < 4; i++) { this.interleaved[i * 4] = this.vertices[(i * 2)]; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 8e757b1..e387e99 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -306,7 +306,7 @@ { this.calculateVertices(); - renderer.setObjectRenderer(renderer.plugins[this.pluginName]); + renderer.batch.setObjectRenderer(renderer.plugins[this.pluginName]); renderer.plugins[this.pluginName].render(this); } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7952fb3..fe7f597 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -98,7 +98,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.renderer.gl; @@ -133,7 +133,7 @@ // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. - this.renderer.bindVao(null); + this.renderer.geometry.bindVao(null); for (let i = 0; i < this.vaoMax; i++) { @@ -142,7 +142,7 @@ var attributeData = shader.program.attributeData; // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() + this.vaos[i] = this.renderer.geometry.createVao() .addIndex(this.indexBuffer) .addAttribute(this.vertexBuffers[i], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) .addAttribute(this.vertexBuffers[i], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) @@ -410,7 +410,7 @@ /* eslint-enable max-len */ } - this.renderer.bindVao(this.vaos[this.vertexCount]); + this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false); @@ -464,13 +464,13 @@ start() { // this.renderer._bindGLShader(this.shader); - this.renderer.shader.bindShader(this.shader, true); + this.renderer.shader.bind(this.shader, true); this.renderer.shader.syncUniformGroup(this.shader.uniformGroup); if (settings.CAN_UPLOAD_SAME_BUFFER) { // bind buffer #0, we don't need others - this.renderer.bindVao(this.vaos[this.vertexCount]); + this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); this.vertexBuffers[this.vertexCount].bind(); } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index fe82451..1f6014f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -157,7 +157,7 @@ this.tileTransform.updateLocalTransform(); this.uvTransform.update(); - renderer.setObjectRenderer(renderer.plugins[this.pluginName]); + renderer.batch.setObjectRenderer(renderer.plugins[this.pluginName]); renderer.plugins[this.pluginName].render(this); } diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/src/core/renderers/webgl/managers/FramebufferManager.js b/src/core/renderers/webgl/managers/FramebufferManager.js index 645fdc9..42c4556 100644 --- a/src/core/renderers/webgl/managers/FramebufferManager.js +++ b/src/core/renderers/webgl/managers/FramebufferManager.js @@ -20,7 +20,7 @@ * * @private */ - onContextChange() + contextChange() { this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; diff --git a/src/core/renderers/webgl/managers/GeometryManager.js b/src/core/renderers/webgl/managers/GeometryManager.js new file mode 100644 index 0000000..af3da72 --- /dev/null +++ b/src/core/renderers/webgl/managers/GeometryManager.js @@ -0,0 +1,216 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class GeometryManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeVao = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + */ + bind(geometry, glShader) + { + const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + + this.bindVao(vao); + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer._updateID) + { + glBuffer._updateID = buffer._updateID; + // TODO - partial upload?? + + glBuffer.upload(buffer.data, 0); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + * @return {PIXI.glCore.VertexArrayObject} Returns a fresh vao. + */ + initGeometryVao(geometry, glShader) + { + const gl = this.gl; + + this.bindVao(null); + + const vao = this.createVao(); + + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + // first update - and create the buffers! + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[this.CONTEXT_UID]) + { + if (buffer.index) + { + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createVertexBuffer(gl, buffer.data, buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); + } + } + } + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); + } + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const glAttribute = glShader.attributes[j]; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + } + } + + // next update the attributes buffer.. + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + // need to know the shader as it means we can be lazy and let pixi do the work for us.. + // stride, start, type? + vao.addAttribute(glBuffer, + glShader.attributes[j], + attribute.type || 5126, // (5126 = FLOAT) + attribute.normalized, + attribute.stride, + attribute.start, + attribute.instance); + } + + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; + + return vao; + } + + draw(type, size, start, instanceCount) + { + this._activeVao.draw(type, size, start, instanceCount); + } + + /** + * Creates a new VAO from this renderer's context and state. + * + * @return {VertexArrayObject} The new VAO. + */ + createVao() + { + return new glCore.VertexArrayObject(this.gl, this.renderer.state.attribState); + } + + /** + * Changes the current Vao to the one given in parameter + * + * @param {PIXI.VertexArrayObject} vao - the new Vao + * @return {PIXI.WebGLRenderer} Returns itself. + */ + bindVao(vao) + { + if (this._activeVao === vao) + { + return this; + } + + if (vao) + { + vao.bind(); + } + else if (this._activeVao) + { + // TODO this should always be true i think? + this._activeVao.unbind(); + } + + this._activeVao = vao; + + return this; + } +} diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index aad715a..1a2ec61 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -45,7 +45,7 @@ else if (this.enableScissor && !this.scissor && this.renderer._activeRenderTarget.root - && !this.renderer.stencilManager.stencilMaskStack.length + && !this.renderer.stencil.stencilMaskStack.length && maskData.isFastRect()) { const matrix = maskData.worldTransform; @@ -82,7 +82,7 @@ { this.popSpriteMask(target, maskData); } - else if (this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) { this.popScissorMask(target, maskData); } @@ -136,7 +136,7 @@ pushStencilMask(maskData) { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); + this.renderer.stencil.pushStencil(maskData); } /** @@ -146,7 +146,7 @@ popStencilMask() { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); + this.renderer.stencil.popStencil(); } /** diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index 02aee2c..51bcdf0 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -23,6 +23,14 @@ null, null, null, + null, + null, + null, + null, + null, + null, + null, + null, null ]; @@ -34,7 +42,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; @@ -54,6 +62,10 @@ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + for (var i = 0; i < this.boundTextures.length; i++) { + this.bind(null, i); + } } bind(texture, location) @@ -61,7 +73,6 @@ const gl = this.gl; - texture = texture.baseTexture || texture; location = location || 0; @@ -71,23 +82,29 @@ gl.activeTexture(gl.TEXTURE0 + location); } - if(texture && texture.valid) + if(texture) { - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + texture = texture.baseTexture || texture; - gl.bindTexture(texture.target, glTexture.texture); - - if(glTexture.dirtyId !== texture.dirtyId) + if(texture.valid) { - glTexture.dirtyId = texture.dirtyId; - this.updateTexture(texture); - } - this.boundTextures[location] = texture; + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + + gl.bindTexture(texture.target, glTexture.texture); + + if(glTexture.dirtyId !== texture.dirtyId) + { + glTexture.dirtyId = texture.dirtyId; + this.updateTexture(texture); + } + + this.boundTextures[location] = texture; + } } else { - gl.bindTexture(texture.target, this.emptyTextures[texture.target].texture); + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); this.boundTextures[location] = null; } } diff --git a/src/core/renderers/webgl/managers/ProjectionManager.js b/src/core/renderers/webgl/managers/ProjectionManager.js index a9bea4b..752ac4a 100644 --- a/src/core/renderers/webgl/managers/ProjectionManager.js +++ b/src/core/renderers/webgl/managers/ProjectionManager.js @@ -89,4 +89,16 @@ pm.ty = 1 - (sourceFrame.y * pm.d); } } + + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform(matrix) + { + // this._activeRenderTarget.transform = matrix; + } + } diff --git a/src/core/renderers/webgl/managers/ShaderManager.js b/src/core/renderers/webgl/managers/ShaderManager.js new file mode 100644 index 0000000..1c4f5f1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ShaderManager.js @@ -0,0 +1,165 @@ +import WebGLManager from './WebGLManager'; +import { GLShader } from 'pixi-gl-core'; +import { PRECISION } from '../../../const'; +import generateUniformsSync from '../../../shader/generateUniformsSync2'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI + */ +export default class ShaderManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. + */ + bind(shader, dontSync) + { + const program = shader.program; + const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + // if (this.shader !== shader) + // { + if (this.shader !== shader) + { + this.shader = shader; + glShader.bind(); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glShader; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); + } + + setUniformsGroups(uniformGroups) + { + + const glShader = this.getGLShader(); + + const group = uniformGroups[0]; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context + */ + getGLShader() + { + if(this.shader) + { + return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; + } + else + { + return null; + } + } + + /** + * Generates a GLShader verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glShader will be based on. + * @return {PIXI.glCore.GLShader} A shiney new GLShader + */ + generateShader(shader) + { + const program = shader.program; + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); + + program.glShaders[this.renderer.CONTEXT_UID] = glShader; + + return glShader; + } + + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderManager + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/managers/StateManager.js b/src/core/renderers/webgl/managers/StateManager.js index 5f7f584..cc10a28 100755 --- a/src/core/renderers/webgl/managers/StateManager.js +++ b/src/core/renderers/webgl/managers/StateManager.js @@ -1,4 +1,5 @@ import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; +import WebGLManager from './WebGLManager'; import WebGLState from '../State'; const BLEND = 0; @@ -13,35 +14,23 @@ * @memberof PIXI * @class */ -export default class StateManager +export default class StateManager extends WebGLManager { /** * @param {WebGLRenderingContext} gl - The current WebGL rendering context */ - constructor(gl) + constructor(renderer) { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(renderer); - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + this.gl = null; - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); + this.maxAttribs = null; // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); + this.nativeVaoExtension = null; + + this.attribState = null; this.stateId = 0; this.polygonOffset = 0; @@ -62,7 +51,37 @@ this.defaultState.blend = true; this.defaultState.depth = true; + + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + this.setState(this.defaultState); + + this.reset(); } /** @@ -211,7 +230,7 @@ /** * Resets all the logic and disables the vaos */ - resetToDefault() + reset() { // unbind any VAO if they exist.. if (this.nativeVaoExtension) @@ -224,6 +243,8 @@ this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + this.setBlendMode(0); + // TODO? // this.setState(this.defaultState); } diff --git a/src/core/renderers/webgl/managers/TextureGCManager.js b/src/core/renderers/webgl/managers/TextureGCManager.js new file mode 100644 index 0000000..c7c93b3 --- /dev/null +++ b/src/core/renderers/webgl/managers/TextureGCManager.js @@ -0,0 +1,111 @@ +import WebGLManager from './WebGLManager'; +import { GC_MODES } from '../../../const'; +import settings from '../../../settings'; + +/** + * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged + * up with textures that are no longer being used. + * + * @class + * @memberof PIXI + */ +export default class TextureGCManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.count = 0; + this.checkCount = 0; + this.maxIdle = settings.GC_MAX_IDLE; + this.checkCountMax = settings.GC_MAX_CHECK_COUNT; + this.mode = settings.GC_MODE; + } + + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + postrender() + { + this.count++; + + if (this.mode === GC_MODES.MANUAL) + { + return; + } + + this.checkCount++; + + if (this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); + } + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() + { + const tm = this.renderer.textureManager; + const managedTextures = tm._managedTextures; + let wasRemoved = false; + + for (let i = 0; i < managedTextures.length; i++) + { + const texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + { + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; + } + } + + if (wasRemoved) + { + let j = 0; + + for (let i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } + } + + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. + */ + unload(displayObject) + { + const tm = this.renderer.textureManager; + + // only destroy non generated textures + if (displayObject._texture && displayObject._texture._glRenderTargets) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (let i = displayObject.children.length - 1; i >= 0; i--) + { + this.unload(displayObject.children[i]); + } + } +} diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 62f97a8..59e4ba8 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -16,14 +16,14 @@ */ this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.runners.contextChange.add(this); } /** * Generic method called when there is a WebGL context change. * */ - onContextChange() + contextChange() { // do some codes init! } @@ -34,8 +34,7 @@ */ destroy() { - this.renderer.off('context', this.onContextChange, this); - + this.renderer.runners.contextChange.remove(this); this.renderer = null; } } diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 218fca5..f1f3571 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -1,26 +1,21 @@ import glCore from 'pixi-gl-core'; import createIndicesForQuads from '../../../utils/createIndicesForQuads'; - +import Geometry from '../../../geometry/Geometry' /** * Helper class to create a quad * * @class * @memberof PIXI */ -export default class Quad +export default class Quad extends Geometry { /** * @param {WebGLRenderingContext} gl - The gl context for this quad to use. * @param {object} state - TODO: Description */ - constructor(gl, state) + constructor() { - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(); /** * An array of vertices @@ -56,6 +51,7 @@ this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1]; } + /* * @member {Uint16Array} An array containing the indices of the vertices */ @@ -64,17 +60,21 @@ /* * @member {glCore.GLBuffer} The vertex buffer */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); /* * @member {glCore.GLBuffer} The index buffer */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); /* * @member {glCore.VertexArrayObject} The index buffer */ - this.vao = new glCore.VertexArrayObject(gl, state); + // this.vao = new glCore.VertexArrayObject(gl, state); + + this.addAttribute('aVertexPosition', this.vertices) + .addAttribute('aTextureCoord', this.uvs) + .addIndex(this.indices); } /** @@ -84,6 +84,7 @@ */ initVao(shader) { + return; this.vao.clear() .addIndex(this.indexBuffer) .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) @@ -139,6 +140,10 @@ */ upload() { + this.getAttribute('aVertexPosition').update(); + this.getAttribute('aTextureCoord').update(); + + return; for (let i = 0; i < 4; i++) { this.interleaved[i * 4] = this.vertices[(i * 2)]; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 8e757b1..e387e99 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -306,7 +306,7 @@ { this.calculateVertices(); - renderer.setObjectRenderer(renderer.plugins[this.pluginName]); + renderer.batch.setObjectRenderer(renderer.plugins[this.pluginName]); renderer.plugins[this.pluginName].render(this); } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7952fb3..fe7f597 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -98,7 +98,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.renderer.gl; @@ -133,7 +133,7 @@ // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. - this.renderer.bindVao(null); + this.renderer.geometry.bindVao(null); for (let i = 0; i < this.vaoMax; i++) { @@ -142,7 +142,7 @@ var attributeData = shader.program.attributeData; // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() + this.vaos[i] = this.renderer.geometry.createVao() .addIndex(this.indexBuffer) .addAttribute(this.vertexBuffers[i], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) .addAttribute(this.vertexBuffers[i], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) @@ -410,7 +410,7 @@ /* eslint-enable max-len */ } - this.renderer.bindVao(this.vaos[this.vertexCount]); + this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false); @@ -464,13 +464,13 @@ start() { // this.renderer._bindGLShader(this.shader); - this.renderer.shader.bindShader(this.shader, true); + this.renderer.shader.bind(this.shader, true); this.renderer.shader.syncUniformGroup(this.shader.uniformGroup); if (settings.CAN_UPLOAD_SAME_BUFFER) { // bind buffer #0, we don't need others - this.renderer.bindVao(this.vaos[this.vertexCount]); + this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); this.vertexBuffers[this.vertexCount].bind(); } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index fe82451..1f6014f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -157,7 +157,7 @@ this.tileTransform.updateLocalTransform(); this.uvTransform.update(); - renderer.setObjectRenderer(renderer.plugins[this.pluginName]); + renderer.batch.setObjectRenderer(renderer.plugins[this.pluginName]); renderer.plugins[this.pluginName].render(this); } diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 5c1a80b..d792dd3 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -26,8 +26,18 @@ { super(renderer); - this.shader = null; - this.simpleShader = null; + const uniforms = {globals:this.renderer.globalUniforms}; + + this.shader = new core.Shader.from( + readFileSync(join(__dirname, './tilingSprite.vert'), 'utf8'), + readFileSync(join(__dirname, './tilingSprite.frag'), 'utf8'), + uniforms); + + this.simpleShader = new core.Shader.from( + readFileSync(join(__dirname, './tilingSprite.vert'), 'utf8'), + readFileSync(join(__dirname, './tilingSprite_simple.frag'), 'utf8'), + uniforms); + this.quad = null; } @@ -36,10 +46,10 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.renderer.gl; - +/* this.shader = new GLShader(gl, readFileSync(join(__dirname, './tilingSprite.vert'), 'utf8'), readFileSync(join(__dirname, './tilingSprite.frag'), 'utf8'), @@ -48,10 +58,9 @@ readFileSync(join(__dirname, './tilingSprite.vert'), 'utf8'), readFileSync(join(__dirname, './tilingSprite_simple.frag'), 'utf8'), core.PRECISION.DEFAULT); - - this.renderer.bindVao(null); - this.quad = new core.Quad(gl, this.renderer.state.attribState); - this.quad.initVao(this.shader); +*/ + this.renderer.geometry.bindVao(null); + this.quad = new core.Quad(); } /** @@ -63,7 +72,6 @@ const renderer = this.renderer; const quad = this.quad; - renderer.bindVao(quad.vao); let vertices = quad.vertices; @@ -84,7 +92,7 @@ vertices[5] = vertices[7] = 1.0 - ts.anchor.y; } - quad.upload(); + // quad.upload(); const tex = ts._texture; const baseTex = tex.baseTexture; @@ -111,7 +119,6 @@ const shader = isSimple ? this.simpleShader : this.shader; - renderer._bindGLShader(shader); const w = tex.width; const h = tex.height; @@ -151,11 +158,11 @@ color[3] = ts.worldAlpha; shader.uniforms.uColor = color; shader.uniforms.translationMatrix = ts.transform.worldTransform.toArray(true); - shader.uniforms.uSampler = renderer.texture.bind(tex.baseTexture, 0); + shader.uniforms.uSampler = tex;//renderer.texture.bind(tex.baseTexture, 0); - renderer.setBlendMode(ts.blendMode); - - quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0); + renderer.shader.bind(shader); + renderer.geometry.bind(quad, renderer.shader.getGLShader()); + renderer.geometry.draw(this.renderer.gl.TRIANGLES, 6, 0); } } diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/src/core/renderers/webgl/managers/FramebufferManager.js b/src/core/renderers/webgl/managers/FramebufferManager.js index 645fdc9..42c4556 100644 --- a/src/core/renderers/webgl/managers/FramebufferManager.js +++ b/src/core/renderers/webgl/managers/FramebufferManager.js @@ -20,7 +20,7 @@ * * @private */ - onContextChange() + contextChange() { this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; diff --git a/src/core/renderers/webgl/managers/GeometryManager.js b/src/core/renderers/webgl/managers/GeometryManager.js new file mode 100644 index 0000000..af3da72 --- /dev/null +++ b/src/core/renderers/webgl/managers/GeometryManager.js @@ -0,0 +1,216 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class GeometryManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeVao = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + */ + bind(geometry, glShader) + { + const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + + this.bindVao(vao); + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer._updateID) + { + glBuffer._updateID = buffer._updateID; + // TODO - partial upload?? + + glBuffer.upload(buffer.data, 0); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + * @return {PIXI.glCore.VertexArrayObject} Returns a fresh vao. + */ + initGeometryVao(geometry, glShader) + { + const gl = this.gl; + + this.bindVao(null); + + const vao = this.createVao(); + + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + // first update - and create the buffers! + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[this.CONTEXT_UID]) + { + if (buffer.index) + { + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createVertexBuffer(gl, buffer.data, buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); + } + } + } + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); + } + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const glAttribute = glShader.attributes[j]; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + } + } + + // next update the attributes buffer.. + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + // need to know the shader as it means we can be lazy and let pixi do the work for us.. + // stride, start, type? + vao.addAttribute(glBuffer, + glShader.attributes[j], + attribute.type || 5126, // (5126 = FLOAT) + attribute.normalized, + attribute.stride, + attribute.start, + attribute.instance); + } + + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; + + return vao; + } + + draw(type, size, start, instanceCount) + { + this._activeVao.draw(type, size, start, instanceCount); + } + + /** + * Creates a new VAO from this renderer's context and state. + * + * @return {VertexArrayObject} The new VAO. + */ + createVao() + { + return new glCore.VertexArrayObject(this.gl, this.renderer.state.attribState); + } + + /** + * Changes the current Vao to the one given in parameter + * + * @param {PIXI.VertexArrayObject} vao - the new Vao + * @return {PIXI.WebGLRenderer} Returns itself. + */ + bindVao(vao) + { + if (this._activeVao === vao) + { + return this; + } + + if (vao) + { + vao.bind(); + } + else if (this._activeVao) + { + // TODO this should always be true i think? + this._activeVao.unbind(); + } + + this._activeVao = vao; + + return this; + } +} diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index aad715a..1a2ec61 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -45,7 +45,7 @@ else if (this.enableScissor && !this.scissor && this.renderer._activeRenderTarget.root - && !this.renderer.stencilManager.stencilMaskStack.length + && !this.renderer.stencil.stencilMaskStack.length && maskData.isFastRect()) { const matrix = maskData.worldTransform; @@ -82,7 +82,7 @@ { this.popSpriteMask(target, maskData); } - else if (this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) { this.popScissorMask(target, maskData); } @@ -136,7 +136,7 @@ pushStencilMask(maskData) { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); + this.renderer.stencil.pushStencil(maskData); } /** @@ -146,7 +146,7 @@ popStencilMask() { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); + this.renderer.stencil.popStencil(); } /** diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index 02aee2c..51bcdf0 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -23,6 +23,14 @@ null, null, null, + null, + null, + null, + null, + null, + null, + null, + null, null ]; @@ -34,7 +42,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; @@ -54,6 +62,10 @@ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + for (var i = 0; i < this.boundTextures.length; i++) { + this.bind(null, i); + } } bind(texture, location) @@ -61,7 +73,6 @@ const gl = this.gl; - texture = texture.baseTexture || texture; location = location || 0; @@ -71,23 +82,29 @@ gl.activeTexture(gl.TEXTURE0 + location); } - if(texture && texture.valid) + if(texture) { - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + texture = texture.baseTexture || texture; - gl.bindTexture(texture.target, glTexture.texture); - - if(glTexture.dirtyId !== texture.dirtyId) + if(texture.valid) { - glTexture.dirtyId = texture.dirtyId; - this.updateTexture(texture); - } - this.boundTextures[location] = texture; + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + + gl.bindTexture(texture.target, glTexture.texture); + + if(glTexture.dirtyId !== texture.dirtyId) + { + glTexture.dirtyId = texture.dirtyId; + this.updateTexture(texture); + } + + this.boundTextures[location] = texture; + } } else { - gl.bindTexture(texture.target, this.emptyTextures[texture.target].texture); + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); this.boundTextures[location] = null; } } diff --git a/src/core/renderers/webgl/managers/ProjectionManager.js b/src/core/renderers/webgl/managers/ProjectionManager.js index a9bea4b..752ac4a 100644 --- a/src/core/renderers/webgl/managers/ProjectionManager.js +++ b/src/core/renderers/webgl/managers/ProjectionManager.js @@ -89,4 +89,16 @@ pm.ty = 1 - (sourceFrame.y * pm.d); } } + + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform(matrix) + { + // this._activeRenderTarget.transform = matrix; + } + } diff --git a/src/core/renderers/webgl/managers/ShaderManager.js b/src/core/renderers/webgl/managers/ShaderManager.js new file mode 100644 index 0000000..1c4f5f1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ShaderManager.js @@ -0,0 +1,165 @@ +import WebGLManager from './WebGLManager'; +import { GLShader } from 'pixi-gl-core'; +import { PRECISION } from '../../../const'; +import generateUniformsSync from '../../../shader/generateUniformsSync2'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI + */ +export default class ShaderManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. + */ + bind(shader, dontSync) + { + const program = shader.program; + const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + // if (this.shader !== shader) + // { + if (this.shader !== shader) + { + this.shader = shader; + glShader.bind(); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glShader; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); + } + + setUniformsGroups(uniformGroups) + { + + const glShader = this.getGLShader(); + + const group = uniformGroups[0]; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context + */ + getGLShader() + { + if(this.shader) + { + return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; + } + else + { + return null; + } + } + + /** + * Generates a GLShader verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glShader will be based on. + * @return {PIXI.glCore.GLShader} A shiney new GLShader + */ + generateShader(shader) + { + const program = shader.program; + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); + + program.glShaders[this.renderer.CONTEXT_UID] = glShader; + + return glShader; + } + + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderManager + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/managers/StateManager.js b/src/core/renderers/webgl/managers/StateManager.js index 5f7f584..cc10a28 100755 --- a/src/core/renderers/webgl/managers/StateManager.js +++ b/src/core/renderers/webgl/managers/StateManager.js @@ -1,4 +1,5 @@ import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; +import WebGLManager from './WebGLManager'; import WebGLState from '../State'; const BLEND = 0; @@ -13,35 +14,23 @@ * @memberof PIXI * @class */ -export default class StateManager +export default class StateManager extends WebGLManager { /** * @param {WebGLRenderingContext} gl - The current WebGL rendering context */ - constructor(gl) + constructor(renderer) { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(renderer); - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + this.gl = null; - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); + this.maxAttribs = null; // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); + this.nativeVaoExtension = null; + + this.attribState = null; this.stateId = 0; this.polygonOffset = 0; @@ -62,7 +51,37 @@ this.defaultState.blend = true; this.defaultState.depth = true; + + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + this.setState(this.defaultState); + + this.reset(); } /** @@ -211,7 +230,7 @@ /** * Resets all the logic and disables the vaos */ - resetToDefault() + reset() { // unbind any VAO if they exist.. if (this.nativeVaoExtension) @@ -224,6 +243,8 @@ this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + this.setBlendMode(0); + // TODO? // this.setState(this.defaultState); } diff --git a/src/core/renderers/webgl/managers/TextureGCManager.js b/src/core/renderers/webgl/managers/TextureGCManager.js new file mode 100644 index 0000000..c7c93b3 --- /dev/null +++ b/src/core/renderers/webgl/managers/TextureGCManager.js @@ -0,0 +1,111 @@ +import WebGLManager from './WebGLManager'; +import { GC_MODES } from '../../../const'; +import settings from '../../../settings'; + +/** + * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged + * up with textures that are no longer being used. + * + * @class + * @memberof PIXI + */ +export default class TextureGCManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.count = 0; + this.checkCount = 0; + this.maxIdle = settings.GC_MAX_IDLE; + this.checkCountMax = settings.GC_MAX_CHECK_COUNT; + this.mode = settings.GC_MODE; + } + + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + postrender() + { + this.count++; + + if (this.mode === GC_MODES.MANUAL) + { + return; + } + + this.checkCount++; + + if (this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); + } + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() + { + const tm = this.renderer.textureManager; + const managedTextures = tm._managedTextures; + let wasRemoved = false; + + for (let i = 0; i < managedTextures.length; i++) + { + const texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + { + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; + } + } + + if (wasRemoved) + { + let j = 0; + + for (let i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } + } + + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. + */ + unload(displayObject) + { + const tm = this.renderer.textureManager; + + // only destroy non generated textures + if (displayObject._texture && displayObject._texture._glRenderTargets) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (let i = displayObject.children.length - 1; i >= 0; i--) + { + this.unload(displayObject.children[i]); + } + } +} diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 62f97a8..59e4ba8 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -16,14 +16,14 @@ */ this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.runners.contextChange.add(this); } /** * Generic method called when there is a WebGL context change. * */ - onContextChange() + contextChange() { // do some codes init! } @@ -34,8 +34,7 @@ */ destroy() { - this.renderer.off('context', this.onContextChange, this); - + this.renderer.runners.contextChange.remove(this); this.renderer = null; } } diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 218fca5..f1f3571 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -1,26 +1,21 @@ import glCore from 'pixi-gl-core'; import createIndicesForQuads from '../../../utils/createIndicesForQuads'; - +import Geometry from '../../../geometry/Geometry' /** * Helper class to create a quad * * @class * @memberof PIXI */ -export default class Quad +export default class Quad extends Geometry { /** * @param {WebGLRenderingContext} gl - The gl context for this quad to use. * @param {object} state - TODO: Description */ - constructor(gl, state) + constructor() { - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(); /** * An array of vertices @@ -56,6 +51,7 @@ this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1]; } + /* * @member {Uint16Array} An array containing the indices of the vertices */ @@ -64,17 +60,21 @@ /* * @member {glCore.GLBuffer} The vertex buffer */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); /* * @member {glCore.GLBuffer} The index buffer */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); /* * @member {glCore.VertexArrayObject} The index buffer */ - this.vao = new glCore.VertexArrayObject(gl, state); + // this.vao = new glCore.VertexArrayObject(gl, state); + + this.addAttribute('aVertexPosition', this.vertices) + .addAttribute('aTextureCoord', this.uvs) + .addIndex(this.indices); } /** @@ -84,6 +84,7 @@ */ initVao(shader) { + return; this.vao.clear() .addIndex(this.indexBuffer) .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) @@ -139,6 +140,10 @@ */ upload() { + this.getAttribute('aVertexPosition').update(); + this.getAttribute('aTextureCoord').update(); + + return; for (let i = 0; i < 4; i++) { this.interleaved[i * 4] = this.vertices[(i * 2)]; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 8e757b1..e387e99 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -306,7 +306,7 @@ { this.calculateVertices(); - renderer.setObjectRenderer(renderer.plugins[this.pluginName]); + renderer.batch.setObjectRenderer(renderer.plugins[this.pluginName]); renderer.plugins[this.pluginName].render(this); } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7952fb3..fe7f597 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -98,7 +98,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.renderer.gl; @@ -133,7 +133,7 @@ // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. - this.renderer.bindVao(null); + this.renderer.geometry.bindVao(null); for (let i = 0; i < this.vaoMax; i++) { @@ -142,7 +142,7 @@ var attributeData = shader.program.attributeData; // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() + this.vaos[i] = this.renderer.geometry.createVao() .addIndex(this.indexBuffer) .addAttribute(this.vertexBuffers[i], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) .addAttribute(this.vertexBuffers[i], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) @@ -410,7 +410,7 @@ /* eslint-enable max-len */ } - this.renderer.bindVao(this.vaos[this.vertexCount]); + this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false); @@ -464,13 +464,13 @@ start() { // this.renderer._bindGLShader(this.shader); - this.renderer.shader.bindShader(this.shader, true); + this.renderer.shader.bind(this.shader, true); this.renderer.shader.syncUniformGroup(this.shader.uniformGroup); if (settings.CAN_UPLOAD_SAME_BUFFER) { // bind buffer #0, we don't need others - this.renderer.bindVao(this.vaos[this.vertexCount]); + this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); this.vertexBuffers[this.vertexCount].bind(); } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index fe82451..1f6014f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -157,7 +157,7 @@ this.tileTransform.updateLocalTransform(); this.uvTransform.update(); - renderer.setObjectRenderer(renderer.plugins[this.pluginName]); + renderer.batch.setObjectRenderer(renderer.plugins[this.pluginName]); renderer.plugins[this.pluginName].render(this); } diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 5c1a80b..d792dd3 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -26,8 +26,18 @@ { super(renderer); - this.shader = null; - this.simpleShader = null; + const uniforms = {globals:this.renderer.globalUniforms}; + + this.shader = new core.Shader.from( + readFileSync(join(__dirname, './tilingSprite.vert'), 'utf8'), + readFileSync(join(__dirname, './tilingSprite.frag'), 'utf8'), + uniforms); + + this.simpleShader = new core.Shader.from( + readFileSync(join(__dirname, './tilingSprite.vert'), 'utf8'), + readFileSync(join(__dirname, './tilingSprite_simple.frag'), 'utf8'), + uniforms); + this.quad = null; } @@ -36,10 +46,10 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.renderer.gl; - +/* this.shader = new GLShader(gl, readFileSync(join(__dirname, './tilingSprite.vert'), 'utf8'), readFileSync(join(__dirname, './tilingSprite.frag'), 'utf8'), @@ -48,10 +58,9 @@ readFileSync(join(__dirname, './tilingSprite.vert'), 'utf8'), readFileSync(join(__dirname, './tilingSprite_simple.frag'), 'utf8'), core.PRECISION.DEFAULT); - - this.renderer.bindVao(null); - this.quad = new core.Quad(gl, this.renderer.state.attribState); - this.quad.initVao(this.shader); +*/ + this.renderer.geometry.bindVao(null); + this.quad = new core.Quad(); } /** @@ -63,7 +72,6 @@ const renderer = this.renderer; const quad = this.quad; - renderer.bindVao(quad.vao); let vertices = quad.vertices; @@ -84,7 +92,7 @@ vertices[5] = vertices[7] = 1.0 - ts.anchor.y; } - quad.upload(); + // quad.upload(); const tex = ts._texture; const baseTex = tex.baseTexture; @@ -111,7 +119,6 @@ const shader = isSimple ? this.simpleShader : this.shader; - renderer._bindGLShader(shader); const w = tex.width; const h = tex.height; @@ -151,11 +158,11 @@ color[3] = ts.worldAlpha; shader.uniforms.uColor = color; shader.uniforms.translationMatrix = ts.transform.worldTransform.toArray(true); - shader.uniforms.uSampler = renderer.texture.bind(tex.baseTexture, 0); + shader.uniforms.uSampler = tex;//renderer.texture.bind(tex.baseTexture, 0); - renderer.setBlendMode(ts.blendMode); - - quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0); + renderer.shader.bind(shader); + renderer.geometry.bind(quad, renderer.shader.getGLShader()); + renderer.geometry.draw(this.renderer.gl.TRIANGLES, 6, 0); } } diff --git a/src/mesh/geometry/GeometryData.js b/src/mesh/geometry/GeometryData.js deleted file mode 100644 index 65321e2..0000000 --- a/src/mesh/geometry/GeometryData.js +++ /dev/null @@ -1,120 +0,0 @@ -/* eslint-disable max-len */ -import Buffer from './Buffer'; - -/** - * GeometryData - the data of the geometry - this consits of attribute buffers and one index buffer. - * - * This can include anything from positions, uvs, normals, colors etc.. - * - * ```js - * let geometryData = new PIXI.mesh.GeometryData(); - * - * geometryData.add('positions', [0,1,0,2,3]); - * geometryData.add('uvs', [0,0,1,0,1,1,0,1]); - * geometryData.addIndex([0,1,2,1,3,2]) - * - * ``` - * @class - * @memberof PIXI.mesh.GeometryData - */ -export default class GeometryData -{ - /** - * - */ - constructor() - { - /** - * an array of {PIXI.mesh.Buffer} belonging to the geometryData - * @type {Array} - */ - this.buffers = []; - - /** - * the index buffer data for the geometry - * @type {PIXI.mesh.Buffer} - */ - this.indexBuffer = null; - } - - /** - * - * Adds an buffer to the geometryData - * - * @param {String} id - the name of the buffer (matching up to a geometry style) - * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data mapping to a geometry attribute. You can also provide an Array and a buffer will be created from it. - * - * @return {PIXI.mesh.GeometryData} returns self, useful for chaining. - */ - add(id, buffer) - { - // bit of duplicate code here and in geometry.. - if (!buffer.data) - { - // its an array! - if (buffer instanceof Array) - { - buffer = new Float32Array(buffer); - } - - buffer = new Buffer(buffer); - } - - // only one! - if (this.buffers.indexOf(buffer) === -1) - { - this.buffers.push(buffer); - this[id] = buffer; - } - - return this; - } - - /** - * - * Adds an index buffer to the geometryData - * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. - * - * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. - * @return {PIXI.mesh.GeometryData} returns self, useful for chaining. - */ - addIndex(buffer) - { - if (!buffer.data) - { - // its an array! - if (buffer instanceof Array) - { - buffer = new Uint16Array(buffer); - } - - buffer = new Buffer(buffer); - } - - buffer.index = true; - this.indexBuffer = buffer; - - if (this.buffers.indexOf(buffer) === -1) - { - this.buffers.push(buffer); - } - - return this; - } - - /** - * Destroys the geometry data. - */ - destroy() - { - for (let i = 0; i < this.buffers.length; i++) - { - this.buffers[i].destroy(); - } - - this.buffers = null; - - this.indexBuffer.destroy(); - this.indexBuffer = null; - } -} diff --git a/package.json b/package.json index 71ade15..a134818 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", diff --git a/src/core/Shader.js b/src/core/Shader.js deleted file mode 100644 index 78b17e6..0000000 --- a/src/core/Shader.js +++ /dev/null @@ -1,46 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import settings from './settings'; - -function checkPrecision(src, def) -{ - if (src instanceof Array) - { - if (src[0].substring(0, 9) !== 'precision') - { - const copy = src.slice(0); - - copy.unshift(`precision ${def} float;`); - - return copy; - } - } - else if (src.substring(0, 9) !== 'precision') - { - return `precision ${def} float;\n${src}`; - } - - return src; -} - -/** - * Wrapper class, webGL Shader for Pixi. - * Adds precision string if vertexSrc or fragmentSrc have no mention of it. - * - * @class - * @extends GLShader - * @memberof PIXI - */ -export default class Shader extends GLShader -{ - /** - * - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - * @param {string|string[]} vertexSrc - The vertex shader source as an array of strings. - * @param {string|string[]} fragmentSrc - The fragment shader source as an array of strings. - */ - constructor(gl, vertexSrc, fragmentSrc) - { - super(gl, checkPrecision(vertexSrc, settings.PRECISION_VERTEX), - checkPrecision(fragmentSrc, settings.PRECISION_FRAGMENT)); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 17fde8d..8abd8be 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -455,7 +455,7 @@ if (mask) { - renderer.maskManager.pushMask(this, this._mask); + renderer.mask.pushMask(this, this._mask); } // add this object to the batch, only rendered if it has a texture. @@ -471,7 +471,7 @@ if (mask) { - renderer.maskManager.popMask(this, this._mask); + renderer.mask.popMask(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) @@ -517,7 +517,7 @@ if (this._mask) { - renderer.maskManager.pushMask(this._mask); + renderer.mask.pushMask(this._mask); } this._renderCanvas(renderer); @@ -528,7 +528,7 @@ if (this._mask) { - renderer.maskManager.popMask(renderer); + renderer.mask.popMask(renderer); } } diff --git a/src/core/geometry/Attribute.js b/src/core/geometry/Attribute.js new file mode 100644 index 0000000..83f92c2 --- /dev/null +++ b/src/core/geometry/Attribute.js @@ -0,0 +1,58 @@ +/* eslint-disable max-len */ + +/** + * holds the information for a single attribute structure required to render geometry. + * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} + * This can include anything from positions, uvs, normals, colors etc.. + * + * @class + * @memberof PIXI.mesh.Attribute + */ +class Attribute +{ + /** + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + */ + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) + { + this.buffer = buffer; + this.size = size; + this.normalized = normalised; + this.type = type; + this.stride = stride; + this.start = start; + this.instance = instance; + } + + /** + * Destroys the Attribute. + */ + destroy() + { + this.buffer = null; + } + + /** + * Helper function that creates an Attribute based on the information provided + * + * @static + * @param {string} buffer the id of the buffer that this attribute will look for + * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * @param {Boolean} [normalised=false] should the data be normalised. + * + * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided + */ + static from(buffer, size, stride, start, normalised) + { + return new Attribute(buffer, size, stride, start, normalised); + } +} + +module.exports = Attribute; diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js new file mode 100644 index 0000000..43d49bf --- /dev/null +++ b/src/core/geometry/Buffer.js @@ -0,0 +1,80 @@ +let UID = 0; +/* eslint-disable max-len */ + +/** + * A wrapper for data so that it can be used and uploaded by webGL + * + * @class + * @memberof PIXI + */ +export default class Buffer +{ + /** + * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. + */ + constructor(data) + { + /** + * The data in the buffer, as a typed array + * + * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray + */ + this.data = data; + + /** + * A map of renderer IDs to webgl buffer + * + * @private + * @member {object} + */ + this._glBuffers = []; + + this._updateID = 0; + + this.index = false; + + this.static = true; + + this.id = UID++; + } + + // TODO could explore flagging only a partial upload? + /** + * flags this buffer as requiring an upload to the GPU + */ + update() + { + this._updateID++; + } + + /** + * Destroys the buffer + */ + destroy() + { + for (let i = 0; i < this._glBuffers.length; i++) + { + this._glBuffers[i].destroy(); + } + + this.data = null; + } + + /** + * Helper function that creates a buffer based on an array or TypedArray + * + * @static + * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. + * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. + */ + static from(data) + { + if (data instanceof Array) + { + data = new Float32Array(data); + } + + return new Buffer(data); + } +} + diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js new file mode 100644 index 0000000..297a86b --- /dev/null +++ b/src/core/geometry/Geometry.js @@ -0,0 +1,389 @@ +import Attribute from './Attribute'; +import Buffer from './Buffer'; +import interleaveTypedArrays from '../utils/interleaveTypedArrays'; +import getBufferType from '../utils/getBufferType'; + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; +let UID = 0; + +/* eslint-disable object-shorthand */ +const map = { + Float32Array: Float32Array, + Uint32Array: Uint32Array, + Int32Array: Int32Array, + Uint16Array: Uint16Array, +}; + +/* eslint-disable max-len */ + +/** + * The Geometry represents a model. It consists of two components: + * GeometryStyle - The structure of the model such as the attributes layout + * GeometryData - the data of the model - this consits of buffers. + * + * This can include anything from positions, uvs, normals, colors etc.. + * + * Geometry can be defined without passing in a style or data if required (thats how I prefer!) + * + * ```js + * let geometry = new PIXI.mesh.Geometry(); + * + * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); + * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) + * geometry.addIndex([0,1,2,1,3,2]) + * + * ``` + * @class + * @memberof PIXI.mesh.Geometry + */ +export default class Geometry +{ + /** + * @param {array} buffers an array of buffers. optional. + * @param {object} attributes of the geometry, optional structure of the attributes layout + */ + constructor(buffers, attributes) + { + this.buffers = buffers || []; + + this.indexBuffer = null; + + this.attributes = attributes || {}; + + /** + * A map of renderer IDs to webgl VAOs + * + * @private + * @type {Array} + */ + this.glVertexArrayObjects = []; + + this.id = UID++; + } + + /** + * + * Adds an attribute to the geometry + * + * @param {String} id - the name of the attribute (matching up to a shader) + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. + * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 + * @param {Boolean} [normalised=false] should the data be normalised. + * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available + * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) + * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) + { + if (!buffer) + { + throw new Error('You must pass a buffer when creating an attribute'); + } + + // check if this is a buffer! + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Float32Array(buffer); + } + + buffer = new Buffer(buffer); + } + + const ids = id.split('|'); + + if (ids.length > 1) + { + for (let i = 0; i < ids.length; i++) + { + this.addAttribute(ids[i], buffer, size, normalised, type); + } + + return this; + } + + let bufferIndex = this.buffers.indexOf(buffer); + + if (bufferIndex === -1) + { + this.buffers.push(buffer); + bufferIndex = this.buffers.length - 1; + } + + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + + return this; + } + + /** + * returns the requested attribute + * + * @param {String} id the name of the attribute required + * @return {PIXI.mesh.Attribute} the attribute requested. + */ + getAttribute(id) + { + return this.buffers[this.attributes[id].buffer]; + } + + /** + * + * Adds an index buffer to the geometry + * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. + * + * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + addIndex(buffer) + { + if (!buffer.data) + { + // its an array! + if (buffer instanceof Array) + { + buffer = new Uint16Array(buffer); + } + + buffer = new Buffer(buffer); + } + + buffer.index = true; + this.indexBuffer = buffer; + + if (this.buffers.indexOf(buffer) === -1) + { + this.buffers.push(buffer); + } + + return this; + } + + /** + * returns the index buffer + * + * @return {PIXI.mesh.Buffer} the index buffer. + */ + getIndex() + { + return this.indexBuffer; + } + + /** + * this function modifies the structure so that all current attributes become interleaved into a single buffer + * This can be useful if your model remains static as it offers a little performance boost + * + * @return {PIXI.mesh.Geometry} returns self, useful for chaining. + */ + interleave() + { + // a simple check to see if buffers are already interleaved.. + if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; + + // assume already that no buffers are interleaved + const arrays = []; + const sizes = []; + const interleavedBuffer = new Buffer(); + let i; + + for (i in this.attributes) + { + const attribute = this.attributes[i]; + + const buffer = this.buffers[attribute.buffer]; + + arrays.push(buffer.data); + + sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); + + attribute.buffer = 0; + } + + interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); + + for (i = 0; i < this.buffers.length; i++) + { + if (this.buffers[i] !== this.indexBuffer) + { + this.buffers[i].destroy(); + } + } + + this.buffers = [interleavedBuffer]; + + if (this.indexBuffer) + { + this.buffers.push(this.indexBuffer); + } + + return this; + } + + /** + * Destroys the geometry. + */ + destroy() + { + for (let i = 0; i < this.glVertexArrayObjects.length; i++) + { + this.glVertexArrayObjects[i].destroy(); + } + + this.glVertexArrayObjects = null; + + for (let i = 0; i < this.buffers.length; i++) + { + this.buffers[i].destroy(); + } + + this.buffers = null; + this.indexBuffer.destroy(); + + this.attributes = null; + } + + /** + * returns a clone of the geometry + * + * @returns {PIXI.mesh.Geometry} a new clone of this geometry + */ + clone() + { + const geometry = new Geometry(); + + for (let i = 0; i < this.buffers.length; i++) + { + geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); + } + + for (const i in this.attributes) + { + const attrib = this.attributes[i]; + + geometry.attributes[i] = new Attribute( + attrib.buffer, + attrib.size, + attrib.normalized, + attrib.type, + attrib.stride, + attrib.start, + attrib.instance + ); + } + + if (this.indexBuffer) + { + geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; + geometry.indexBuffer.index = true; + } + + return geometry; + } + + /** + * merges an array of geometries into a new single one + * geometry attribute styles must match for this operation to work + * + * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge + * @returns {PIXI.mesh.Geometry} shiney new geometry + */ + static merge(geometries) + { + // todo add a geometry check! + // also a size check.. cant be too big!] + + const geometryOut = new Geometry(); + + const arrays = []; + const sizes = []; + const offsets = []; + + let geometry; + + // pass one.. get sizes.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + sizes[j] = sizes[j] || 0; + sizes[j] += geometry.buffers[j].data.length; + offsets[j] = 0; + } + } + + // build the correct size arrays.. + for (let i = 0; i < geometry.buffers.length; i++) + { + // TODO types! + arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); + geometryOut.buffers[i] = new Buffer(arrays[i]); + } + + // pass to set data.. + for (let i = 0; i < geometries.length; i++) + { + geometry = geometries[i]; + + for (let j = 0; j < geometry.buffers.length; j++) + { + arrays[j].set(geometry.buffers[j].data, offsets[j]); + offsets[j] += geometry.buffers[j].data.length; + } + } + + geometryOut.attributes = geometry.attributes; + + if (geometry.indexBuffer) + { + geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; + geometryOut.indexBuffer.index = true; + + let offset = 0; + let stride = 0; + let offset2 = 0; + let bufferIndexToCount = 0; + + // get a buffer + for (let i = 0; i < geometry.buffers.length; i++) + { + if (geometry.buffers[i] !== geometry.indexBuffer) + { + bufferIndexToCount = i; + break; + } + } + + // figure out the stride of one buffer.. + for (const i in geometry.attributes) + { + const attribute = geometry.attributes[i]; + + if ((attribute.buffer | 0) === bufferIndexToCount) + { + stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); + } + } + + // time to off set all indexes.. + for (let i = 0; i < geometries.length; i++) + { + const indexBufferData = geometries[i].indexBuffer.data; + + for (let j = 0; j < indexBufferData.length; j++) + { + geometryOut.indexBuffer.data[j + offset2] += offset; + } + + offset += geometry.buffers[bufferIndexToCount].data.length / (stride); + offset2 += indexBufferData.length; + } + } + + return geometryOut; + } +} diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js deleted file mode 100644 index cf7b433..0000000 --- a/src/core/renderers/webgl/ShaderManager.js +++ /dev/null @@ -1,163 +0,0 @@ -import { GLShader } from 'pixi-gl-core'; -import { PRECISION } from '../../const'; -import generateUniformsSync from '../../shader/generateUniformsSync2'; - -let UID = 0; - -/** - * Helper class to create a webGL Texture - * - * @class - * @memberof PIXI - */ -export default class ShaderManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer - */ - constructor(renderer) - { - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - this.shader = null; - - this.id = UID++; - } - - /** - * Changes the current shader to the one given in parameter - * - * @param {PIXI.Shader} shader - the new shader - * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. - * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. - */ - bindShader(shader, dontSync) - { - const program = shader.program; - const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); - - // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. - // if (this.shader !== shader) - // { - if (this.shader !== shader) - { - this.shader = shader; - glShader.bind(); - } - - if (!dontSync) - { - this.setUniforms(shader.uniforms); - } - - return glShader; - } - - /** - * Uploads the uniforms values to the currently bound shader. - * - * @param {object} uniforms - the uniforms valiues that be applied to the current shader - */ - setUniforms(uniforms) - { - const shader = this.shader.program; - const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; - - shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); - } - - setUniformsGroups(uniformGroups) - { - - const glShader = this.getGLShader(); - - const group = uniformGroups[0]; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - - } - - syncUniformGroup(group) - { - const glShader = this.getGLShader(); - - if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) - { - glShader.uniformGroups[group.id] = group.dirtyId; - const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); - - syncFunc(glShader.uniformData, group.uniforms, this.renderer); - } - - } - - createSynGroups(group) - { - group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); - - return group.syncUniforms[this.shader.program.id]; - } - /** - * Returns the underlying GLShade rof the currently bound shader. - * This can be handy for when you to have a little more control over the setting of your uniforms. - * - * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context - */ - getGLShader() - { - if(this.shader) - { - return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; - } - else - { - return null; - } - } - - /** - * Generates a GLShader verion of the Shader provided. - * - * @private - * @param {PIXI.Shader} shader the shader that the glShader will be based on. - * @return {PIXI.glCore.GLShader} A shiney new GLShader - */ - generateShader(shader) - { - const program = shader.program; - const attribMap = {}; - - for (const i in program.attributeData) - { - attribMap[i] = program.attributeData[i].location; - } - - const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); - - program.glShaders[this.renderer.CONTEXT_UID] = glShader; - - return glShader; - } - - /** - * Destroys this manager and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderManager - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js deleted file mode 100644 index ee76814..0000000 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ /dev/null @@ -1,109 +0,0 @@ -import { GC_MODES } from '../../const'; -import settings from '../../settings'; - -/** - * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged - * up with textures that are no longer being used. - * - * @class - * @memberof PIXI - */ -export default class TextureGarbageCollector -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = settings.GC_MAX_IDLE; - this.checkCountMax = settings.GC_MAX_CHECK_COUNT; - this.mode = settings.GC_MODE; - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - update() - { - this.count++; - - if (this.mode === GC_MODES.MANUAL) - { - return; - } - - this.checkCount++; - - if (this.checkCount > this.checkCountMax) - { - this.checkCount = 0; - - this.run(); - } - } - - /** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ - run() - { - const tm = this.renderer.textureManager; - const managedTextures = tm._managedTextures; - let wasRemoved = false; - - for (let i = 0; i < managedTextures.length; i++) - { - const texture = managedTextures[i]; - - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) - { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; - } - } - - if (wasRemoved) - { - let j = 0; - - for (let i = 0; i < managedTextures.length; i++) - { - if (managedTextures[i] !== null) - { - managedTextures[j++] = managedTextures[i]; - } - } - - managedTextures.length = j; - } - } - - /** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. - */ - unload(displayObject) - { - const tm = this.renderer.textureManager; - - // only destroy non generated textures - if (displayObject._texture && displayObject._texture._glRenderTargets) - { - tm.destroyTexture(displayObject._texture, true); - } - - for (let i = displayObject.children.length - 1; i >= 0; i--) - { - this.unload(displayObject.children[i]); - } - } -} diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6cfb52f..3c4869a 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -5,24 +5,20 @@ import FramebufferManager from './managers/FramebufferManager'; import RenderTextureManager from './managers/RenderTextureManager'; import NewTextureManager from './managers/NewTextureManager'; -import RenderTarget from './utils/RenderTarget'; -import ObjectRenderer from './utils/ObjectRenderer'; import TextureManager from './TextureManager'; import ProjectionManager from './managers/ProjectionManager'; import StateManager from './managers/StateManager'; -import ShaderManager from './ShaderManager'; -import BaseTexture from '../../textures/BaseTexture'; -import TextureGarbageCollector from './TextureGarbageCollector'; -import mapWebGLDrawModesToPixi from './utils/mapWebGLDrawModesToPixi'; -import validateContext from './utils/validateContext'; +import GeometryManager from './managers/GeometryManager'; +import ShaderManager from './managers/ShaderManager'; +import ContextManager from './managers/ContextManager'; +import BatchManager from './managers/BatchManager'; +import TextureGCManager from './managers/TextureGCManager'; import { pluginTarget } from '../../utils'; import glCore from 'pixi-gl-core'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; - - -let CONTEXT_UID = 0; +import Runner from 'mini-runner'; /** * The WebGLRenderer draws the scene and all its content onto a webGL enabled canvas. This renderer @@ -64,13 +60,6 @@ { super('WebGL', screenWidth, screenHeight, options); - this.legacy = !!options.legacy; - - if (this.legacy) - { - glCore.VertexArrayObject.FORCE_NATIVE = true; - } - /** * The type of this renderer as a standardised const * @@ -79,11 +68,27 @@ */ this.type = RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + // this will be set by the contextManager (this.context) + this.gl = null; + this.CONTEXT_UID = 0; + this.legacy = !!options.legacy; - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + if (this.legacy) + { + glCore.VertexArrayObject.FORCE_NATIVE = true; + } + + // runners! + this.runners = { + destroy: new Runner('destroy'), + contextChange: new Runner('contextChange', 1), + reset: new Runner('reset'), + update: new Runner('update'), + postrender: new Runner('postrender'), + prerender: new Runner('prerender'), + resize: new Runner('resize', 2), + }; + /** * The options passed in to create a new webgl context. @@ -91,120 +96,80 @@ * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer, - }; - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - this.framebuffer = new FramebufferManager(this); - this.texture = new NewTextureManager(this); - this.renderTexture = new RenderTextureManager(this); - this.projection = new ProjectionManager(this); - this.globalUniforms = new UniformGroup({ projectionMatrix:new Matrix() }, true) - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; + this.addManager(MaskManager) + .addManager(ContextManager) + .addManager(StateManager) + .addManager(ShaderManager) + .addManager(NewTextureManager, 'texture') + .addManager(GeometryManager) + .addManager(FramebufferManager) + .addManager(StencilManager) + .addManager(ProjectionManager) + .addManager(TextureGCManager) + .addManager(FilterManager) + .addManager(RenderTextureManager) + .addManager(BatchManager) this.initPlugins(); - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if (options.context) + if(options.context) { - // checks to see if a context is valid.. - validateContext(options.context); + this.context.initFromContext(options.context); } - - this.gl = options.context || glCore.createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ -// this.state = new WebGLState(this.gl); - this.state = new StateManager(this.gl); - this.state.setBlendMode(0); - + else + { + this.context.initFromOptions({ + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + }); + } this.renderingToScreen = true; - /** - * Holds the current state of textures bound to the GPU. - * @type {Array} - */ - this.boundTextures = null; - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - this._activeVao = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - this._nextTextureLocation = 0; + this._initContext(); + } + addManager(_class, name) + { + if(!name) + { + name = _class.name; + } + //TODO - read name from class.name.. + if(name.includes('Manager')) + { + name = name.replace('Manager', ''); + name = name.charAt(0).toLowerCase() + name.slice(1); + } + + const manager = new _class(this); + + if(this[name]) + { + throw new Error('Whoops! ' + name + ' is already a manger'); + return; + } + + this[name] = manager; + + for(var i in this.runners) + { + this.runners[i].add(manager); + } + + return this; } /** @@ -216,48 +181,18 @@ { const gl = this.gl; - // restore a context if it was previously lost - if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').restoreContext(); - } - const maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); this.boundTextures = new Array(maxTextures); this.emptyTextures = new Array(maxTextures); - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.shader = new ShaderManager(this); - - this.state.resetToDefault(); - - // now lets fill up the textures with empty ones! - const emptyGLTexture = new glCore.GLTexture.fromData(gl, null, 1, 1); - const tempObj = { _glTextures: {} }; - tempObj._glTextures[this.CONTEXT_UID] = {}; - for (let i = 0; i < maxTextures; i++) { - const empty = new BaseTexture(); - - empty._glTextures[this.CONTEXT_UID] = emptyGLTexture; - this.boundTextures[i] = tempObj; - this.emptyTextures[i] = empty; - this.bindTexture(null, i); } - this.emit('context', gl); - - // set the latest testing context.. - glCore._testingContext = gl; - // setup the width/height properties and gl viewport this.resize(this.screen.width, this.screen.height); } @@ -276,14 +211,14 @@ // can be handy to know! this.renderingToScreen = !renderTexture; + this.runners.prerender.run(); this.emit('prerender'); // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) + if(this.context.isLost) { return; } - this._nextTextureLocation = 0; if (!renderTexture) @@ -303,8 +238,7 @@ } this.renderTexture.bind(renderTexture); - - this.currentRenderer.start(); + this.batch.currentRenderer.start(); if (clear !== undefined ? clear : this.clearBeforeRender) { @@ -314,47 +248,13 @@ displayObject.renderWebGL(this); // apply transform.. - this.currentRenderer.flush(); + this.batch.currentRenderer.flush(); - // this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.gl.flush(); - + this.runners.postrender.run(); this.emit('postrender'); } /** - * Changes the current renderer to the one given in parameter - * - * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. - */ - setObjectRenderer(objectRenderer) - { - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.state.setState(objectRenderer.state); - - this.currentRenderer.start(); - } - - /** - * This should be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ - flush() - { - this.setObjectRenderer(this.emptyRenderer); - } - - /** * Resizes the webGL view to the specified width and height. * * @param {number} screenWidth - the new width of the screen @@ -362,156 +262,8 @@ */ resize(screenWidth, screenHeight) { - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - SystemRenderer.prototype.resize.call(this, screenWidth, screenHeight); - - this.renderTexture.resize(screenWidth, screenHeight); - } - - /** - * Sets the transform of the active render target to the given matrix - * - * @param {PIXI.Matrix} matrix - The transformation matrix - */ - setTransform(matrix) - { - this._activeRenderTarget.transform = matrix; - } - - /** - * Binds the texture. This will return the location of the bound texture. - * It may not be the same as the one you pass in. This is due to optimisation that prevents - * needless binding of textures. For example if the texture is already bound it will return the - * current location of the texture instead of the one provided. To bypass this use force location - * - * @param {PIXI.Texture} texture - the new texture - * @param {number} location - the suggested texture location - * @param {boolean} forceLocation - force the location - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindTexture(texture, location, forceLocation) - { - texture = texture || this.emptyTextures[location]; - texture = texture.baseTexture || texture; - texture.touched = this.textureGC.count; - - if (!forceLocation) - { - // TODO - maybe look into adding boundIds.. save us the loop? - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - return i; - } - } - - if (location === undefined) - { - this._nextTextureLocation++; - this._nextTextureLocation %= this.boundTextures.length; - location = this.boundTextures.length - this._nextTextureLocation - 1; - } - } - else - { - location = location || 0; - } - - const gl = this.gl; - let glTexture = texture._glTextures[this.CONTEXT_UID]; - - if(texture._newTexture) - { - this.newTextureManager.bindTexture(texture._newTexture, location); - glTexture = texture._newTexture.glTextures[this.CONTEXT_UID]; - - return location; - } - - if (!glTexture) - { - // this will also bind the texture.. - this.textureManager.updateTexture(texture, location); - } - else - { - if (this.boundTextures[location] === texture) - { - return location; - } - - this.boundTextures[location] = texture; - gl.activeTexture(gl.TEXTURE0 + location); - gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); - } - - return location; - } - - /** - * unbinds the texture ... - * - * @param {PIXI.Texture} texture - the texture to unbind - * @return {PIXI.WebGLRenderer} Returns itself. - */ - unbindTexture(texture) - { - const gl = this.gl; - - texture = texture.baseTexture || texture; - - for (let i = 0; i < this.boundTextures.length; i++) - { - if (this.boundTextures[i] === texture) - { - this.boundTextures[i] = this.emptyTextures[i]; - - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[i]._glTextures[this.CONTEXT_UID].texture); - } - } - - return this; - } - - /** - * Creates a new VAO from this renderer's context and state. - * - * @return {VertexArrayObject} The new VAO. - */ - createVao() - { - return new glCore.VertexArrayObject(this.gl, this.state.attribState); - } - - /** - * Changes the current Vao to the one given in parameter - * - * @param {PIXI.VertexArrayObject} vao - the new Vao - * @return {PIXI.WebGLRenderer} Returns itself. - */ - bindVao(vao) - { - if (this._activeVao === vao) - { - return this; - } - - if (vao) - { - vao.bind(); - } - else if (this._activeVao) - { - // TODO this should always be true i think? - this._activeVao.unbind(); - } - - this._activeVao = vao; - - return this; + this.runners.resize.run(screenWidth, screenHeight); } /** @@ -521,42 +273,11 @@ */ reset() { - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - + this.runners.reset.run(); return this; } /** - * Handles a lost webgl context - * - * @private - * @param {WebGLContextEvent} event - The context lost event. - */ - handleContextLost(event) - { - event.preventDefault(); - } - - /** - * Handles a restored webgl context - * - * @private - */ - handleContextRestored() - { - this._initContext(); - this.textureManager.removeAll(); - } - - /** * Removes everything from the renderer (event listeners, spritebatch, etc...) * * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM. @@ -564,43 +285,14 @@ */ destroy(removeView) { - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - // call base destroy super.destroy(removeView); - this.uid = 0; + this.destroyPlugins(); + this.runners.destroy.run(); - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if (this.gl.getExtension('WEBGL_lose_context')) - { - this.gl.getExtension('WEBGL_lose_context').loseContext(); - } - + // TODO nullify all the managers.. this.gl = null; - - // this = null; } } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js deleted file mode 100755 index 6c846d5..0000000 --- a/src/core/renderers/webgl/WebGLState.js +++ /dev/null @@ -1,263 +0,0 @@ -import mapWebGLBlendModesToPixi from './utils/mapWebGLBlendModesToPixi'; - -const BLEND = 0; -const DEPTH_TEST = 1; -const FRONT_FACE = 2; -const CULL_FACE = 3; -const BLEND_FUNC = 4; - -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - */ -export default class WebGLState -{ - /** - * @param {WebGLRenderingContext} gl - The current WebGL rendering context - */ - constructor(gl) - { - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); - } - - /** - * Pushes a new active state - */ - push() - { - // next state.. - let state = this.stack[this.stackIndex]; - - if (!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - ++this.stackIndex; - - // copy state.. - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; i++) - { - state[i] = this.activeState[i]; - } - } - - /** - * Pops a state out - */ - pop() - { - const state = this.stack[--this.stackIndex]; - - this.setState(state); - } - - /** - * Sets the current state - * - * @param {*} state - The state to set. - */ - setState(state) - { - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); - } - - /** - * Enables or disabled blending. - * - * @param {boolean} value - Turn on or off webgl blending. - */ - setBlend(value) - { - value = value ? 1 : 0; - - if (this.activeState[BLEND] === value) - { - return; - } - - this.activeState[BLEND] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.BLEND); - } - - /** - * Sets the blend mode. - * - * @param {number} value - The blend mode to set to. - */ - setBlendMode(value) - { - if (value === this.activeState[BLEND_FUNC]) - { - return; - } - - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); - } - - /** - * Sets whether to enable or disable depth test. - * - * @param {boolean} value - Turn on or off webgl depth testing. - */ - setDepthTest(value) - { - value = value ? 1 : 0; - - if (this.activeState[DEPTH_TEST] === value) - { - return; - } - - this.activeState[DEPTH_TEST] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.DEPTH_TEST); - } - - /** - * Sets whether to enable or disable cull face. - * - * @param {boolean} value - Turn on or off webgl cull face. - */ - setCullFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[CULL_FACE] === value) - { - return; - } - - this.activeState[CULL_FACE] = value; - this.gl[value ? 'enable' : 'disable'](this.gl.CULL_FACE); - } - - /** - * Sets the gl front face. - * - * @param {boolean} value - true is clockwise and false is counter-clockwise - */ - setFrontFace(value) - { - value = value ? 1 : 0; - - if (this.activeState[FRONT_FACE] === value) - { - return; - } - - this.activeState[FRONT_FACE] = value; - this.gl.frontFace(this.gl[value ? 'CW' : 'CCW']); - } - - /** - * Disables all the vaos in use - * - */ - resetAttributes() - { - for (let i = 0; i < this.attribState.tempAttribState.length; i++) - { - this.attribState.tempAttribState[i] = 0; - } - - for (let i = 0; i < this.attribState.attribState.length; i++) - { - this.attribState.attribState[i] = 0; - } - - // im going to assume one is always active for performance reasons. - for (let i = 1; i < this.maxAttribs; i++) - { - this.gl.disableVertexAttribArray(i); - } - } - - // used - /** - * Resets all the logic and disables the vaos - */ - resetToDefault() - { - // unbind any VAO if they exist.. - if (this.nativeVaoExtension) - { - this.nativeVaoExtension.bindVertexArrayOES(null); - } - - // reset all attributes.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (let i = 0; i < this.activeState.length; ++i) - { - this.activeState[i] = 32; - } - - this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); - - this.setState(this.defaultState); - } -} diff --git a/src/core/renderers/webgl/managers/BatchManager.js b/src/core/renderers/webgl/managers/BatchManager.js new file mode 100644 index 0000000..ebda417 --- /dev/null +++ b/src/core/renderers/webgl/managers/BatchManager.js @@ -0,0 +1,69 @@ +import WebGLManager from './WebGLManager'; +import ObjectRenderer from '../utils/ObjectRenderer'; +import { Rectangle, Matrix } from '../../../math'; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class BatchManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(renderer); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param {PIXI.ObjectRenderer} objectRenderer - The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + + this.renderer.state.setState(objectRenderer.state); + + this.currentRenderer.start(); + } + + /** + * This should be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + reset() + { + this.setObjectRenderer(this.emptyRenderer); + } +} diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js deleted file mode 100644 index 204ea2e..0000000 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ /dev/null @@ -1,45 +0,0 @@ -import WebGLManager from './WebGLManager'; - -/** - * @class - * @memberof PIXI - * @extends PIXI.WebGLManager - */ -export default class BlendModeManager extends WebGLManager -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. - */ - constructor(renderer) - { - super(renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; - } - - /** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param {number} blendMode - the blendMode, should be a Pixi const, such as - * `PIXI.BLEND_MODES.ADD`. See {@link PIXI.BLEND_MODES} for possible values. - * @return {boolean} Returns if the blend mode was changed. - */ - setBlendMode(blendMode) - { - if (this.currentBlendMode === blendMode) - { - return false; - } - - this.currentBlendMode = blendMode; - - const mode = this.renderer.blendModes[this.currentBlendMode]; - - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; - } -} diff --git a/src/core/renderers/webgl/managers/ContextManager.js b/src/core/renderers/webgl/managers/ContextManager.js new file mode 100644 index 0000000..ccde2b1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ContextManager.js @@ -0,0 +1,119 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; +import validateContext from '../utils/validateContext'; + +let CONTEXT_UID = 0; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class ContextManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + + renderer.view.addEventListener('webglcontextlost', this.handleContextLost, false); + renderer.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + } + + get isLost() + { + return (!this.gl || this.gl.isContextLost()); + } + + contextChange(gl) + { + this.gl = gl; + + // restore a context if it was previously lost + if (gl.isContextLost() && gl.getExtension('WEBGL_lose_context')) + { + gl.getExtension('WEBGL_lose_context').restoreContext(); + } + + // set the latest testing context.. + if(!glCore._testingContext) + { + glCore._testingContext = gl; + } + + // setup the width/height properties and gl viewport + //this.resize(this.screen.width, this.screen.height); + // const renderer = this.renderer; + + // renderer.resize(renderer.screen.width, renderer.screen.height); + } + + initFromContext(gl) + { + this.gl = gl; + validateContext(gl); + this.renderer.gl = gl; + this.renderer.CONTEXT_UID = CONTEXT_UID++; + this.renderer.runners.contextChange.run(gl); + } + + initFromOptions(options) + { + const gl = glCore.createContext(this.renderer.view, this.options); + this.initFromContext(gl); + } + + /** + * Handles a lost webgl context + * + * @private + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) + { + event.preventDefault(); + } + + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this.renderer.runners.contextChange.run(gl); + + // TODO - tidy up textures? + //this.textureManager.removeAll(); + } + + destroy() + { + const view = this.renderer.view; + + // remove listeners + view.removeEventListener('webglcontextlost', this.handleContextLost); + view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.gl.useProgram(null); + + if (this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + } + + postrender() + { + this.gl.flush(); + } +} diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 6c7422d..09104d7 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -40,9 +40,6 @@ { super(renderer); - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); this.shaderCache = {}; // todo add default! @@ -51,6 +48,14 @@ this.filterData = null; } + contextChange() + { + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, this.renderer.state.attribState); + + } + /** * Adds a new filter to the manager. * diff --git a/src/core/renderers/webgl/managers/FramebufferManager.js b/src/core/renderers/webgl/managers/FramebufferManager.js index 645fdc9..42c4556 100644 --- a/src/core/renderers/webgl/managers/FramebufferManager.js +++ b/src/core/renderers/webgl/managers/FramebufferManager.js @@ -20,7 +20,7 @@ * * @private */ - onContextChange() + contextChange() { this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; diff --git a/src/core/renderers/webgl/managers/GeometryManager.js b/src/core/renderers/webgl/managers/GeometryManager.js new file mode 100644 index 0000000..af3da72 --- /dev/null +++ b/src/core/renderers/webgl/managers/GeometryManager.js @@ -0,0 +1,216 @@ +import WebGLManager from './WebGLManager'; +import { Rectangle, Matrix } from '../../../math'; +import glCore from 'pixi-gl-core'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLManager + * @memberof PIXI + */ + +export default class GeometryManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this._activeVao = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + } + + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + */ + bind(geometry, glShader) + { + const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + + this.bindVao(vao); + + // TODO - optimise later! + // don't need to loop through if nothing changed! + // maybe look to add an 'autoupdate' to geometry? + for (let i = 0; i < geometry.buffers.length; i++) + { + const buffer = geometry.buffers[i]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + if (buffer._updateID !== glBuffer._updateID) + { + glBuffer._updateID = buffer._updateID; + // TODO - partial upload?? + + glBuffer.upload(buffer.data, 0); + } + } + } + + /** + * Creates a Vao with the same structure as the geometry and stores it on the geometry. + * @private + * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for + * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. + * @return {PIXI.glCore.VertexArrayObject} Returns a fresh vao. + */ + initGeometryVao(geometry, glShader) + { + const gl = this.gl; + + this.bindVao(null); + + const vao = this.createVao(); + + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + // first update - and create the buffers! + for (let i = 0; i < buffers.length; i++) + { + const buffer = buffers[i]; + + if (!buffer._glBuffers[this.CONTEXT_UID]) + { + if (buffer.index) + { + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createVertexBuffer(gl, buffer.data, buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); + } + } + } + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); + } + + const tempStride = {}; + const tempStart = {}; + + for (const j in buffers) + { + tempStride[j] = 0; + tempStart[j] = 0; + } + + for (const j in attributes) + { + tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + } + + for (const j in attributes) + { + const attribute = attributes[j]; + const glAttribute = glShader.attributes[j]; + + if (attribute.stride === undefined) + { + if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + { + attribute.stride = 0; + } + else + { + attribute.stride = tempStride[attribute.buffer]; + } + } + + if (attribute.start === undefined) + { + attribute.start = tempStart[attribute.buffer]; + + tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + } + } + + // next update the attributes buffer.. + for (const j in attributes) + { + const attribute = attributes[j]; + const buffer = buffers[attribute.buffer]; + + const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + + // need to know the shader as it means we can be lazy and let pixi do the work for us.. + // stride, start, type? + vao.addAttribute(glBuffer, + glShader.attributes[j], + attribute.type || 5126, // (5126 = FLOAT) + attribute.normalized, + attribute.stride, + attribute.start, + attribute.instance); + } + + geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; + + return vao; + } + + draw(type, size, start, instanceCount) + { + this._activeVao.draw(type, size, start, instanceCount); + } + + /** + * Creates a new VAO from this renderer's context and state. + * + * @return {VertexArrayObject} The new VAO. + */ + createVao() + { + return new glCore.VertexArrayObject(this.gl, this.renderer.state.attribState); + } + + /** + * Changes the current Vao to the one given in parameter + * + * @param {PIXI.VertexArrayObject} vao - the new Vao + * @return {PIXI.WebGLRenderer} Returns itself. + */ + bindVao(vao) + { + if (this._activeVao === vao) + { + return this; + } + + if (vao) + { + vao.bind(); + } + else if (this._activeVao) + { + // TODO this should always be true i think? + this._activeVao.unbind(); + } + + this._activeVao = vao; + + return this; + } +} diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index aad715a..1a2ec61 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -45,7 +45,7 @@ else if (this.enableScissor && !this.scissor && this.renderer._activeRenderTarget.root - && !this.renderer.stencilManager.stencilMaskStack.length + && !this.renderer.stencil.stencilMaskStack.length && maskData.isFastRect()) { const matrix = maskData.worldTransform; @@ -82,7 +82,7 @@ { this.popSpriteMask(target, maskData); } - else if (this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + else if (this.enableScissor && !this.renderer.stencil.stencilMaskStack.length) { this.popScissorMask(target, maskData); } @@ -136,7 +136,7 @@ pushStencilMask(maskData) { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); + this.renderer.stencil.pushStencil(maskData); } /** @@ -146,7 +146,7 @@ popStencilMask() { this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); + this.renderer.stencil.popStencil(); } /** diff --git a/src/core/renderers/webgl/managers/NewTextureManager.js b/src/core/renderers/webgl/managers/NewTextureManager.js index 02aee2c..51bcdf0 100644 --- a/src/core/renderers/webgl/managers/NewTextureManager.js +++ b/src/core/renderers/webgl/managers/NewTextureManager.js @@ -23,6 +23,14 @@ null, null, null, + null, + null, + null, + null, + null, + null, + null, + null, null ]; @@ -34,7 +42,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; @@ -54,6 +62,10 @@ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + for (var i = 0; i < this.boundTextures.length; i++) { + this.bind(null, i); + } } bind(texture, location) @@ -61,7 +73,6 @@ const gl = this.gl; - texture = texture.baseTexture || texture; location = location || 0; @@ -71,23 +82,29 @@ gl.activeTexture(gl.TEXTURE0 + location); } - if(texture && texture.valid) + if(texture) { - const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + texture = texture.baseTexture || texture; - gl.bindTexture(texture.target, glTexture.texture); - - if(glTexture.dirtyId !== texture.dirtyId) + if(texture.valid) { - glTexture.dirtyId = texture.dirtyId; - this.updateTexture(texture); - } - this.boundTextures[location] = texture; + const glTexture = texture._glTextures[this.CONTEXT_UID] || this.initTexture(texture); + + gl.bindTexture(texture.target, glTexture.texture); + + if(glTexture.dirtyId !== texture.dirtyId) + { + glTexture.dirtyId = texture.dirtyId; + this.updateTexture(texture); + } + + this.boundTextures[location] = texture; + } } else { - gl.bindTexture(texture.target, this.emptyTextures[texture.target].texture); + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); this.boundTextures[location] = null; } } diff --git a/src/core/renderers/webgl/managers/ProjectionManager.js b/src/core/renderers/webgl/managers/ProjectionManager.js index a9bea4b..752ac4a 100644 --- a/src/core/renderers/webgl/managers/ProjectionManager.js +++ b/src/core/renderers/webgl/managers/ProjectionManager.js @@ -89,4 +89,16 @@ pm.ty = 1 - (sourceFrame.y * pm.d); } } + + + /** + * Sets the transform of the active render target to the given matrix + * + * @param {PIXI.Matrix} matrix - The transformation matrix + */ + setTransform(matrix) + { + // this._activeRenderTarget.transform = matrix; + } + } diff --git a/src/core/renderers/webgl/managers/ShaderManager.js b/src/core/renderers/webgl/managers/ShaderManager.js new file mode 100644 index 0000000..1c4f5f1 --- /dev/null +++ b/src/core/renderers/webgl/managers/ShaderManager.js @@ -0,0 +1,165 @@ +import WebGLManager from './WebGLManager'; +import { GLShader } from 'pixi-gl-core'; +import { PRECISION } from '../../../const'; +import generateUniformsSync from '../../../shader/generateUniformsSync2'; + +let UID = 0; + +/** + * Helper class to create a webGL Texture + * + * @class + * @memberof PIXI + */ +export default class ShaderManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - A reference to the current renderer + */ + constructor(renderer) + { + + super(renderer); + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = null; + + this.shader = null; + + this.id = UID++; + } + + contextChange(gl) + { + this.gl = gl; + } + + /** + * Changes the current shader to the one given in parameter + * + * @param {PIXI.Shader} shader - the new shader + * @param {boolean} dontSync - false if the shader should automatically sync its uniforms. + * @returns {PIXI.glCore.GLShader} the glShader that belongs to the shader. + */ + bind(shader, dontSync) + { + const program = shader.program; + const glShader = program.glShaders[this.renderer.CONTEXT_UID] || this.generateShader(shader); + + // TODO - some current pixi plugins bypass this.. so it not safe to use yet.. + // if (this.shader !== shader) + // { + if (this.shader !== shader) + { + this.shader = shader; + glShader.bind(); + } + + if (!dontSync) + { + this.syncUniformGroup(shader.uniformGroup); + } + + return glShader; + } + + /** + * Uploads the uniforms values to the currently bound shader. + * + * @param {object} uniforms - the uniforms valiues that be applied to the current shader + */ + setUniforms(uniforms) + { + const shader = this.shader.program; + const glShader = shader.glShaders[this.renderer.CONTEXT_UID]; + + shader.syncUniforms(glShader.uniformData, uniforms, this.renderer); + } + + setUniformsGroups(uniformGroups) + { + + const glShader = this.getGLShader(); + + const group = uniformGroups[0]; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } + /** + * Returns the underlying GLShade rof the currently bound shader. + * This can be handy for when you to have a little more control over the setting of your uniforms. + * + * @return {PIXI.glCore.Shader} the glShader for the currently bound Shader for this context + */ + getGLShader() + { + if(this.shader) + { + return this.shader.program.glShaders[this.renderer.CONTEXT_UID]; + } + else + { + return null; + } + } + + /** + * Generates a GLShader verion of the Shader provided. + * + * @private + * @param {PIXI.Shader} shader the shader that the glShader will be based on. + * @return {PIXI.glCore.GLShader} A shiney new GLShader + */ + generateShader(shader) + { + const program = shader.program; + const attribMap = {}; + + for (const i in program.attributeData) + { + attribMap[i] = program.attributeData[i].location; + } + + const glShader = new GLShader(this.gl, program.vertexSrc, program.fragmentSrc, PRECISION.DEFAULT, attribMap); + + program.glShaders[this.renderer.CONTEXT_UID] = glShader; + + return glShader; + } + + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderManager + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/managers/StateManager.js b/src/core/renderers/webgl/managers/StateManager.js index 5f7f584..cc10a28 100755 --- a/src/core/renderers/webgl/managers/StateManager.js +++ b/src/core/renderers/webgl/managers/StateManager.js @@ -1,4 +1,5 @@ import mapWebGLBlendModesToPixi from '../utils/mapWebGLBlendModesToPixi'; +import WebGLManager from './WebGLManager'; import WebGLState from '../State'; const BLEND = 0; @@ -13,35 +14,23 @@ * @memberof PIXI * @class */ -export default class StateManager +export default class StateManager extends WebGLManager { /** * @param {WebGLRenderingContext} gl - The current WebGL rendering context */ - constructor(gl) + constructor(renderer) { - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(renderer); - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + this.gl = null; - this.attribState = { - tempAttribState: new Array(this.maxAttribs), - attribState: new Array(this.maxAttribs), - }; - - this.blendModes = mapWebGLBlendModesToPixi(gl); + this.maxAttribs = null; // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') - || gl.getExtension('MOZ_OES_vertex_array_object') - || gl.getExtension('WEBKIT_OES_vertex_array_object') - ); + this.nativeVaoExtension = null; + + this.attribState = null; this.stateId = 0; this.polygonOffset = 0; @@ -62,7 +51,37 @@ this.defaultState.blend = true; this.defaultState.depth = true; + + } + + contextChange(gl) + { + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') + || gl.getExtension('MOZ_OES_vertex_array_object') + || gl.getExtension('WEBKIT_OES_vertex_array_object') + ); + + this.attribState = { + tempAttribState: new Array(this.maxAttribs), + attribState: new Array(this.maxAttribs), + }; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + this.setState(this.defaultState); + + this.reset(); } /** @@ -211,7 +230,7 @@ /** * Resets all the logic and disables the vaos */ - resetToDefault() + reset() { // unbind any VAO if they exist.. if (this.nativeVaoExtension) @@ -224,6 +243,8 @@ this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + this.setBlendMode(0); + // TODO? // this.setState(this.defaultState); } diff --git a/src/core/renderers/webgl/managers/TextureGCManager.js b/src/core/renderers/webgl/managers/TextureGCManager.js new file mode 100644 index 0000000..c7c93b3 --- /dev/null +++ b/src/core/renderers/webgl/managers/TextureGCManager.js @@ -0,0 +1,111 @@ +import WebGLManager from './WebGLManager'; +import { GC_MODES } from '../../../const'; +import settings from '../../../settings'; + +/** + * TextureGarbageCollector. This class manages the GPU and ensures that it does not get clogged + * up with textures that are no longer being used. + * + * @class + * @memberof PIXI + */ +export default class TextureGCManager extends WebGLManager +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for. + */ + constructor(renderer) + { + super(renderer); + + this.count = 0; + this.checkCount = 0; + this.maxIdle = settings.GC_MAX_IDLE; + this.checkCountMax = settings.GC_MAX_CHECK_COUNT; + this.mode = settings.GC_MODE; + } + + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + postrender() + { + this.count++; + + if (this.mode === GC_MODES.MANUAL) + { + return; + } + + this.checkCount++; + + if (this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); + } + } + + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() + { + const tm = this.renderer.textureManager; + const managedTextures = tm._managedTextures; + let wasRemoved = false; + + for (let i = 0; i < managedTextures.length; i++) + { + const texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + { + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; + } + } + + if (wasRemoved) + { + let j = 0; + + for (let i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } + } + + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param {PIXI.DisplayObject} displayObject - the displayObject to remove the textures from. + */ + unload(displayObject) + { + const tm = this.renderer.textureManager; + + // only destroy non generated textures + if (displayObject._texture && displayObject._texture._glRenderTargets) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (let i = displayObject.children.length - 1; i >= 0; i--) + { + this.unload(displayObject.children[i]); + } + } +} diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 62f97a8..59e4ba8 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -16,14 +16,14 @@ */ this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.runners.contextChange.add(this); } /** * Generic method called when there is a WebGL context change. * */ - onContextChange() + contextChange() { // do some codes init! } @@ -34,8 +34,7 @@ */ destroy() { - this.renderer.off('context', this.onContextChange, this); - + this.renderer.runners.contextChange.remove(this); this.renderer = null; } } diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 218fca5..f1f3571 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -1,26 +1,21 @@ import glCore from 'pixi-gl-core'; import createIndicesForQuads from '../../../utils/createIndicesForQuads'; - +import Geometry from '../../../geometry/Geometry' /** * Helper class to create a quad * * @class * @memberof PIXI */ -export default class Quad +export default class Quad extends Geometry { /** * @param {WebGLRenderingContext} gl - The gl context for this quad to use. * @param {object} state - TODO: Description */ - constructor(gl, state) + constructor() { - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; + super(); /** * An array of vertices @@ -56,6 +51,7 @@ this.interleaved[(i * 4) + 3] = this.uvs[(i * 2) + 1]; } + /* * @member {Uint16Array} An array containing the indices of the vertices */ @@ -64,17 +60,21 @@ /* * @member {glCore.GLBuffer} The vertex buffer */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); /* * @member {glCore.GLBuffer} The index buffer */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); /* * @member {glCore.VertexArrayObject} The index buffer */ - this.vao = new glCore.VertexArrayObject(gl, state); + // this.vao = new glCore.VertexArrayObject(gl, state); + + this.addAttribute('aVertexPosition', this.vertices) + .addAttribute('aTextureCoord', this.uvs) + .addIndex(this.indices); } /** @@ -84,6 +84,7 @@ */ initVao(shader) { + return; this.vao.clear() .addIndex(this.indexBuffer) .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) @@ -139,6 +140,10 @@ */ upload() { + this.getAttribute('aVertexPosition').update(); + this.getAttribute('aTextureCoord').update(); + + return; for (let i = 0; i < 4; i++) { this.interleaved[i * 4] = this.vertices[(i * 2)]; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index 8e757b1..e387e99 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -306,7 +306,7 @@ { this.calculateVertices(); - renderer.setObjectRenderer(renderer.plugins[this.pluginName]); + renderer.batch.setObjectRenderer(renderer.plugins[this.pluginName]); renderer.plugins[this.pluginName].render(this); } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 7952fb3..fe7f597 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -98,7 +98,7 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.renderer.gl; @@ -133,7 +133,7 @@ // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. - this.renderer.bindVao(null); + this.renderer.geometry.bindVao(null); for (let i = 0; i < this.vaoMax; i++) { @@ -142,7 +142,7 @@ var attributeData = shader.program.attributeData; // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() + this.vaos[i] = this.renderer.geometry.createVao() .addIndex(this.indexBuffer) .addAttribute(this.vertexBuffers[i], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) .addAttribute(this.vertexBuffers[i], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) @@ -410,7 +410,7 @@ /* eslint-enable max-len */ } - this.renderer.bindVao(this.vaos[this.vertexCount]); + this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false); @@ -464,13 +464,13 @@ start() { // this.renderer._bindGLShader(this.shader); - this.renderer.shader.bindShader(this.shader, true); + this.renderer.shader.bind(this.shader, true); this.renderer.shader.syncUniformGroup(this.shader.uniformGroup); if (settings.CAN_UPLOAD_SAME_BUFFER) { // bind buffer #0, we don't need others - this.renderer.bindVao(this.vaos[this.vertexCount]); + this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); this.vertexBuffers[this.vertexCount].bind(); } diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index fe82451..1f6014f 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -157,7 +157,7 @@ this.tileTransform.updateLocalTransform(); this.uvTransform.update(); - renderer.setObjectRenderer(renderer.plugins[this.pluginName]); + renderer.batch.setObjectRenderer(renderer.plugins[this.pluginName]); renderer.plugins[this.pluginName].render(this); } diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 5c1a80b..d792dd3 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -26,8 +26,18 @@ { super(renderer); - this.shader = null; - this.simpleShader = null; + const uniforms = {globals:this.renderer.globalUniforms}; + + this.shader = new core.Shader.from( + readFileSync(join(__dirname, './tilingSprite.vert'), 'utf8'), + readFileSync(join(__dirname, './tilingSprite.frag'), 'utf8'), + uniforms); + + this.simpleShader = new core.Shader.from( + readFileSync(join(__dirname, './tilingSprite.vert'), 'utf8'), + readFileSync(join(__dirname, './tilingSprite_simple.frag'), 'utf8'), + uniforms); + this.quad = null; } @@ -36,10 +46,10 @@ * * @private */ - onContextChange() + contextChange() { const gl = this.renderer.gl; - +/* this.shader = new GLShader(gl, readFileSync(join(__dirname, './tilingSprite.vert'), 'utf8'), readFileSync(join(__dirname, './tilingSprite.frag'), 'utf8'), @@ -48,10 +58,9 @@ readFileSync(join(__dirname, './tilingSprite.vert'), 'utf8'), readFileSync(join(__dirname, './tilingSprite_simple.frag'), 'utf8'), core.PRECISION.DEFAULT); - - this.renderer.bindVao(null); - this.quad = new core.Quad(gl, this.renderer.state.attribState); - this.quad.initVao(this.shader); +*/ + this.renderer.geometry.bindVao(null); + this.quad = new core.Quad(); } /** @@ -63,7 +72,6 @@ const renderer = this.renderer; const quad = this.quad; - renderer.bindVao(quad.vao); let vertices = quad.vertices; @@ -84,7 +92,7 @@ vertices[5] = vertices[7] = 1.0 - ts.anchor.y; } - quad.upload(); + // quad.upload(); const tex = ts._texture; const baseTex = tex.baseTexture; @@ -111,7 +119,6 @@ const shader = isSimple ? this.simpleShader : this.shader; - renderer._bindGLShader(shader); const w = tex.width; const h = tex.height; @@ -151,11 +158,11 @@ color[3] = ts.worldAlpha; shader.uniforms.uColor = color; shader.uniforms.translationMatrix = ts.transform.worldTransform.toArray(true); - shader.uniforms.uSampler = renderer.texture.bind(tex.baseTexture, 0); + shader.uniforms.uSampler = tex;//renderer.texture.bind(tex.baseTexture, 0); - renderer.setBlendMode(ts.blendMode); - - quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0); + renderer.shader.bind(shader); + renderer.geometry.bind(quad, renderer.shader.getGLShader()); + renderer.geometry.draw(this.renderer.gl.TRIANGLES, 6, 0); } } diff --git a/src/mesh/geometry/GeometryData.js b/src/mesh/geometry/GeometryData.js deleted file mode 100644 index 65321e2..0000000 --- a/src/mesh/geometry/GeometryData.js +++ /dev/null @@ -1,120 +0,0 @@ -/* eslint-disable max-len */ -import Buffer from './Buffer'; - -/** - * GeometryData - the data of the geometry - this consits of attribute buffers and one index buffer. - * - * This can include anything from positions, uvs, normals, colors etc.. - * - * ```js - * let geometryData = new PIXI.mesh.GeometryData(); - * - * geometryData.add('positions', [0,1,0,2,3]); - * geometryData.add('uvs', [0,0,1,0,1,1,0,1]); - * geometryData.addIndex([0,1,2,1,3,2]) - * - * ``` - * @class - * @memberof PIXI.mesh.GeometryData - */ -export default class GeometryData -{ - /** - * - */ - constructor() - { - /** - * an array of {PIXI.mesh.Buffer} belonging to the geometryData - * @type {Array} - */ - this.buffers = []; - - /** - * the index buffer data for the geometry - * @type {PIXI.mesh.Buffer} - */ - this.indexBuffer = null; - } - - /** - * - * Adds an buffer to the geometryData - * - * @param {String} id - the name of the buffer (matching up to a geometry style) - * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data mapping to a geometry attribute. You can also provide an Array and a buffer will be created from it. - * - * @return {PIXI.mesh.GeometryData} returns self, useful for chaining. - */ - add(id, buffer) - { - // bit of duplicate code here and in geometry.. - if (!buffer.data) - { - // its an array! - if (buffer instanceof Array) - { - buffer = new Float32Array(buffer); - } - - buffer = new Buffer(buffer); - } - - // only one! - if (this.buffers.indexOf(buffer) === -1) - { - this.buffers.push(buffer); - this[id] = buffer; - } - - return this; - } - - /** - * - * Adds an index buffer to the geometryData - * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. - * - * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. - * @return {PIXI.mesh.GeometryData} returns self, useful for chaining. - */ - addIndex(buffer) - { - if (!buffer.data) - { - // its an array! - if (buffer instanceof Array) - { - buffer = new Uint16Array(buffer); - } - - buffer = new Buffer(buffer); - } - - buffer.index = true; - this.indexBuffer = buffer; - - if (this.buffers.indexOf(buffer) === -1) - { - this.buffers.push(buffer); - } - - return this; - } - - /** - * Destroys the geometry data. - */ - destroy() - { - for (let i = 0; i < this.buffers.length; i++) - { - this.buffers[i].destroy(); - } - - this.buffers = null; - - this.indexBuffer.destroy(); - this.indexBuffer = null; - } -} diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 3d33be7..9af0f53 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -60,7 +60,7 @@ this.renderer.state.setState(mesh.state); // bind the geometry... - this.bindGeometry(mesh.geometry, glShader); + this.renderer.geometry.bind(mesh.geometry, glShader); // then render it mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } @@ -73,143 +73,6 @@ { mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } - - /** - * Binds geometry so that is can be drawn. Creating a Vao if required - * @private - * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind - * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. - */ - bindGeometry(geometry, glShader) - { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); - - this.renderer.bindVao(vao); - - // TODO - optimise later! - // don't need to loop through if nothing changed! - // maybe look to add an 'autoupdate' to geometry? - for (let i = 0; i < geometry.buffers.length; i++) - { - const buffer = geometry.buffers[i]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - if (buffer._updateID !== glBuffer._updateID) - { - glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - - glBuffer.upload(buffer.data, 0); - } - } - } - - /** - * Creates a Vao with the same structure as the geometry and stores it on the geometry. - * @private - * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for - * @param {PIXI.glCore.glShader} glShader shader that the geometry will be renderered with. - * @return {PIXI.glCore.VertexArrayObject} Returns a fresh vao. - */ - initGeometryVao(geometry, glShader) - { - const gl = this.gl; - - this.renderer.bindVao(null); - - const vao = this.renderer.createVao(); - - const buffers = geometry.buffers; - const attributes = geometry.attributes; - - // first update - and create the buffers! - for (let i = 0; i < buffers.length; i++) - { - const buffer = buffers[i]; - - if (!buffer._glBuffers[this.CONTEXT_UID]) - { - if (buffer.index) - { - buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createIndexBuffer(gl, buffer.data); - } - else - { - /* eslint-disable max-len */ - buffer._glBuffers[this.CONTEXT_UID] = glCore.GLBuffer.createVertexBuffer(gl, buffer.data, buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW); - } - } - } - - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - - const tempStride = {}; - const tempStart = {}; - - for (const j in buffers) - { - tempStride[j] = 0; - tempStart[j] = 0; - } - - for (const j in attributes) - { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; - } - - for (const j in attributes) - { - const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; - - if (attribute.stride === undefined) - { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) - { - attribute.stride = 0; - } - else - { - attribute.stride = tempStride[attribute.buffer]; - } - } - - if (attribute.start === undefined) - { - attribute.start = tempStart[attribute.buffer]; - - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; - } - } - - // next update the attributes buffer.. - for (const j in attributes) - { - const attribute = attributes[j]; - const buffer = buffers[attribute.buffer]; - - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; - - // need to know the shader as it means we can be lazy and let pixi do the work for us.. - // stride, start, type? - vao.addAttribute(glBuffer, - glShader.attributes[j], - attribute.type || 5126, // (5126 = FLOAT) - attribute.normalized, - attribute.stride, - attribute.start, - attribute.instance); - } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; - } } core.WebGLRenderer.registerPlugin('mesh', MeshRenderer);