diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js new file mode 100644 index 0000000..313f078 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js @@ -0,0 +1,41 @@ + +var mapType = require('./mapType'); +var mapSize = require('./mapSize'); + +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the attributes from + * @return attributes {Object} + */ +var extractAttributes = function(gl, program) +{ + var attributes = {}; + + var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + + for (var i = 0; i < totalAttributes; i++) + { + var attribData = gl.getActiveAttrib(program, i); + var type = mapType(gl, attribData.type); + + attributes[attribData.name] = { + type:type, + size:mapSize(type), + location:gl.getAttribLocation(program, attribData.name), + //TODO - make an attribute object + pointer: pointer + }; + } + + return attributes; +}; + +var pointer = function(type, normalized, stride, start){ + // console.log(this.location) + gl.vertexAttribPointer(this.location,this.size, type || gl.FLOAT, normalized || false, stride || 0, start || 0); +}; + +module.exports = extractAttributes; diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js new file mode 100644 index 0000000..313f078 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js @@ -0,0 +1,41 @@ + +var mapType = require('./mapType'); +var mapSize = require('./mapSize'); + +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the attributes from + * @return attributes {Object} + */ +var extractAttributes = function(gl, program) +{ + var attributes = {}; + + var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + + for (var i = 0; i < totalAttributes; i++) + { + var attribData = gl.getActiveAttrib(program, i); + var type = mapType(gl, attribData.type); + + attributes[attribData.name] = { + type:type, + size:mapSize(type), + location:gl.getAttribLocation(program, attribData.name), + //TODO - make an attribute object + pointer: pointer + }; + } + + return attributes; +}; + +var pointer = function(type, normalized, stride, start){ + // console.log(this.location) + gl.vertexAttribPointer(this.location,this.size, type || gl.FLOAT, normalized || false, stride || 0, start || 0); +}; + +module.exports = extractAttributes; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js new file mode 100644 index 0000000..78d9f0d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js @@ -0,0 +1,35 @@ +var mapType = require('./mapType'); +var defaultValue = require('./defaultValue'); + +/** + * Extracts the uniforms + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the uniforms from + * @return uniforms {Object} + */ +var extractUniforms = function(gl, program) +{ + var uniforms = {}; + + var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + + for (var i = 0; i < totalUniforms; i++) + { + var uniformData = gl.getActiveUniform(program, i); + var name = uniformData.name.replace(/\[.*?\]/, ""); + var type = mapType(gl, uniformData.type ); + + uniforms[name] = { + type:type, + size:uniformData.size, + location:gl.getUniformLocation(program, name), + value:defaultValue(type, uniformData.size) + }; + } + + return uniforms; +}; + +module.exports = extractUniforms; diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js new file mode 100644 index 0000000..313f078 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js @@ -0,0 +1,41 @@ + +var mapType = require('./mapType'); +var mapSize = require('./mapSize'); + +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the attributes from + * @return attributes {Object} + */ +var extractAttributes = function(gl, program) +{ + var attributes = {}; + + var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + + for (var i = 0; i < totalAttributes; i++) + { + var attribData = gl.getActiveAttrib(program, i); + var type = mapType(gl, attribData.type); + + attributes[attribData.name] = { + type:type, + size:mapSize(type), + location:gl.getAttribLocation(program, attribData.name), + //TODO - make an attribute object + pointer: pointer + }; + } + + return attributes; +}; + +var pointer = function(type, normalized, stride, start){ + // console.log(this.location) + gl.vertexAttribPointer(this.location,this.size, type || gl.FLOAT, normalized || false, stride || 0, start || 0); +}; + +module.exports = extractAttributes; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js new file mode 100644 index 0000000..78d9f0d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js @@ -0,0 +1,35 @@ +var mapType = require('./mapType'); +var defaultValue = require('./defaultValue'); + +/** + * Extracts the uniforms + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the uniforms from + * @return uniforms {Object} + */ +var extractUniforms = function(gl, program) +{ + var uniforms = {}; + + var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + + for (var i = 0; i < totalUniforms; i++) + { + var uniformData = gl.getActiveUniform(program, i); + var name = uniformData.name.replace(/\[.*?\]/, ""); + var type = mapType(gl, uniformData.type ); + + uniforms[name] = { + type:type, + size:uniformData.size, + location:gl.getUniformLocation(program, name), + value:defaultValue(type, uniformData.size) + }; + } + + return uniforms; +}; + +module.exports = extractUniforms; diff --git a/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js new file mode 100644 index 0000000..c2aa597 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js @@ -0,0 +1,144 @@ +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param uniforms {Array} @mat ? + * @return attributes {Object} + */ +var generateUniformAccessObject = function(gl, uniformData) +{ + // this is the object we will be sending back. + // an object hierachy will be created for structs + var uniforms = {data:{}}; + + uniforms.gl = gl; + + var uniformKeys= Object.keys(uniformData); + + for (var i = 0; i < uniformKeys.length; i++) + { + var fullName = uniformKeys[i]; + + var nameTokens = fullName.split('.'); + var name = nameTokens[nameTokens.length - 1]; + + + var uniformGroup = getUniformGroup(nameTokens, uniforms); + + var uniform = uniformData[fullName]; + uniformGroup.data[name] = uniform; + + uniformGroup.gl = gl; + + Object.defineProperty(uniformGroup, name, { + get: generateGetter(name), + set: generateSetter(name, uniform) + }); + } + + return uniforms; +}; + +var generateGetter = function(name) +{ + var template = getterTemplate.replace('%%', name); + return new Function(template); // jshint ignore:line +}; + +var generateSetter = function(name, uniform) +{ + var template = setterTemplate.replace(/%%/g, name); + var setTemplate; + + if(uniform.size === 1) + { + setTemplate = GLSL_TO_SINGLE_SETTERS[uniform.type]; + } + else + { + setTemplate = GLSL_TO_ARRAY_SETTERS[uniform.type]; + } + + if(setTemplate) + { + template += "\nthis.gl." + setTemplate + ";"; + } + + return new Function('value', template); // jshint ignore:line +}; + +var getUniformGroup = function(nameTokens, uniform) +{ + var cur = uniform; + + for (var i = 0; i < nameTokens.length - 1; i++) + { + var o = cur[nameTokens[i]] || {data:{}}; + cur[nameTokens[i]] = o; + cur = o; + } + + return cur; +}; + +var getterTemplate = [ + 'return this.data.%%.value;', +].join('\n'); + +var setterTemplate = [ + 'this.data.%%.value = value;', + 'var location = this.data.%%.location;' +].join('\n'); + + +var GLSL_TO_SINGLE_SETTERS = { + + 'float': 'uniform1f(location, value)', + + 'vec2': 'uniform2f(location, value[0], value[1])', + 'vec3': 'uniform3f(location, value[0], value[1], value[2])', + 'vec4': 'uniform4f(location, value[0], value[1], value[2], value[3])', + + 'int': 'uniform1i(location, value)', + 'ivec2': 'uniform2i(location, value[0], value[1])', + 'ivec3': 'uniform3i(location, value[0], value[1], value[2])', + 'ivec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'bool': 'uniform1i(location, value)', + 'bvec2': 'uniform2i(location, value[0], value[1])', + 'bvec3': 'uniform3i(location, value[0], value[1], value[2])', + 'bvec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'mat2': 'uniformMatrix2fv(location, false, value)', + 'mat3': 'uniformMatrix3fv(location, false, value)', + 'mat4': 'uniformMatrix4fv(location, false, value)', + + 'sampler2D':'uniform1i(location, value)', + 'samplerCube': 'uniform1i(location, value)' + +}; + +var GLSL_TO_ARRAY_SETTERS = { + + 'float': 'uniform1fv(location, value)', + + 'vec2': 'uniform2fv(location, value)', + 'vec3': 'uniform3fv(location, value)', + 'vec4': 'uniform4fv(location, value)', + + 'int': 'uniform1iv(location, value)', + 'ivec2': 'uniform2iv(location, value)', + 'ivec3': 'uniform3iv(location, value)', + 'ivec4': 'uniform4iv(location, value)', + + 'bool': 'uniform1iv(location, value)', + 'bvec2': 'uniform2iv(location, value)', + 'bvec3': 'uniform3iv(location, value)', + 'bvec4': 'uniform4iv(location, value)', + + 'sampler2D':'uniform1iv(location, value)', + 'samplerCube': 'uniform1i(location, value)' +}; + +module.exports = generateUniformAccessObject; diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js new file mode 100644 index 0000000..313f078 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js @@ -0,0 +1,41 @@ + +var mapType = require('./mapType'); +var mapSize = require('./mapSize'); + +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the attributes from + * @return attributes {Object} + */ +var extractAttributes = function(gl, program) +{ + var attributes = {}; + + var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + + for (var i = 0; i < totalAttributes; i++) + { + var attribData = gl.getActiveAttrib(program, i); + var type = mapType(gl, attribData.type); + + attributes[attribData.name] = { + type:type, + size:mapSize(type), + location:gl.getAttribLocation(program, attribData.name), + //TODO - make an attribute object + pointer: pointer + }; + } + + return attributes; +}; + +var pointer = function(type, normalized, stride, start){ + // console.log(this.location) + gl.vertexAttribPointer(this.location,this.size, type || gl.FLOAT, normalized || false, stride || 0, start || 0); +}; + +module.exports = extractAttributes; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js new file mode 100644 index 0000000..78d9f0d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js @@ -0,0 +1,35 @@ +var mapType = require('./mapType'); +var defaultValue = require('./defaultValue'); + +/** + * Extracts the uniforms + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the uniforms from + * @return uniforms {Object} + */ +var extractUniforms = function(gl, program) +{ + var uniforms = {}; + + var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + + for (var i = 0; i < totalUniforms; i++) + { + var uniformData = gl.getActiveUniform(program, i); + var name = uniformData.name.replace(/\[.*?\]/, ""); + var type = mapType(gl, uniformData.type ); + + uniforms[name] = { + type:type, + size:uniformData.size, + location:gl.getUniformLocation(program, name), + value:defaultValue(type, uniformData.size) + }; + } + + return uniforms; +}; + +module.exports = extractUniforms; diff --git a/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js new file mode 100644 index 0000000..c2aa597 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js @@ -0,0 +1,144 @@ +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param uniforms {Array} @mat ? + * @return attributes {Object} + */ +var generateUniformAccessObject = function(gl, uniformData) +{ + // this is the object we will be sending back. + // an object hierachy will be created for structs + var uniforms = {data:{}}; + + uniforms.gl = gl; + + var uniformKeys= Object.keys(uniformData); + + for (var i = 0; i < uniformKeys.length; i++) + { + var fullName = uniformKeys[i]; + + var nameTokens = fullName.split('.'); + var name = nameTokens[nameTokens.length - 1]; + + + var uniformGroup = getUniformGroup(nameTokens, uniforms); + + var uniform = uniformData[fullName]; + uniformGroup.data[name] = uniform; + + uniformGroup.gl = gl; + + Object.defineProperty(uniformGroup, name, { + get: generateGetter(name), + set: generateSetter(name, uniform) + }); + } + + return uniforms; +}; + +var generateGetter = function(name) +{ + var template = getterTemplate.replace('%%', name); + return new Function(template); // jshint ignore:line +}; + +var generateSetter = function(name, uniform) +{ + var template = setterTemplate.replace(/%%/g, name); + var setTemplate; + + if(uniform.size === 1) + { + setTemplate = GLSL_TO_SINGLE_SETTERS[uniform.type]; + } + else + { + setTemplate = GLSL_TO_ARRAY_SETTERS[uniform.type]; + } + + if(setTemplate) + { + template += "\nthis.gl." + setTemplate + ";"; + } + + return new Function('value', template); // jshint ignore:line +}; + +var getUniformGroup = function(nameTokens, uniform) +{ + var cur = uniform; + + for (var i = 0; i < nameTokens.length - 1; i++) + { + var o = cur[nameTokens[i]] || {data:{}}; + cur[nameTokens[i]] = o; + cur = o; + } + + return cur; +}; + +var getterTemplate = [ + 'return this.data.%%.value;', +].join('\n'); + +var setterTemplate = [ + 'this.data.%%.value = value;', + 'var location = this.data.%%.location;' +].join('\n'); + + +var GLSL_TO_SINGLE_SETTERS = { + + 'float': 'uniform1f(location, value)', + + 'vec2': 'uniform2f(location, value[0], value[1])', + 'vec3': 'uniform3f(location, value[0], value[1], value[2])', + 'vec4': 'uniform4f(location, value[0], value[1], value[2], value[3])', + + 'int': 'uniform1i(location, value)', + 'ivec2': 'uniform2i(location, value[0], value[1])', + 'ivec3': 'uniform3i(location, value[0], value[1], value[2])', + 'ivec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'bool': 'uniform1i(location, value)', + 'bvec2': 'uniform2i(location, value[0], value[1])', + 'bvec3': 'uniform3i(location, value[0], value[1], value[2])', + 'bvec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'mat2': 'uniformMatrix2fv(location, false, value)', + 'mat3': 'uniformMatrix3fv(location, false, value)', + 'mat4': 'uniformMatrix4fv(location, false, value)', + + 'sampler2D':'uniform1i(location, value)', + 'samplerCube': 'uniform1i(location, value)' + +}; + +var GLSL_TO_ARRAY_SETTERS = { + + 'float': 'uniform1fv(location, value)', + + 'vec2': 'uniform2fv(location, value)', + 'vec3': 'uniform3fv(location, value)', + 'vec4': 'uniform4fv(location, value)', + + 'int': 'uniform1iv(location, value)', + 'ivec2': 'uniform2iv(location, value)', + 'ivec3': 'uniform3iv(location, value)', + 'ivec4': 'uniform4iv(location, value)', + + 'bool': 'uniform1iv(location, value)', + 'bvec2': 'uniform2iv(location, value)', + 'bvec3': 'uniform3iv(location, value)', + 'bvec4': 'uniform4iv(location, value)', + + 'sampler2D':'uniform1iv(location, value)', + 'samplerCube': 'uniform1i(location, value)' +}; + +module.exports = generateUniformAccessObject; diff --git a/src/core/renderers/webgl/systems/shader/shader/index.js b/src/core/renderers/webgl/systems/shader/shader/index.js new file mode 100644 index 0000000..e08684d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/index.js @@ -0,0 +1,10 @@ +module.exports = { + compileProgram: require('./compileProgram'), + defaultValue: require('./defaultValue'), + extractAttributes: require('./extractAttributes'), + extractUniforms: require('./extractUniforms'), + generateUniformAccessObject: require('./generateUniformAccessObject'), + setPrecision: require('./setPrecision'), + mapSize: require('./mapSize'), + mapType: require('./mapType') +}; \ No newline at end of file diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js new file mode 100644 index 0000000..313f078 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js @@ -0,0 +1,41 @@ + +var mapType = require('./mapType'); +var mapSize = require('./mapSize'); + +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the attributes from + * @return attributes {Object} + */ +var extractAttributes = function(gl, program) +{ + var attributes = {}; + + var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + + for (var i = 0; i < totalAttributes; i++) + { + var attribData = gl.getActiveAttrib(program, i); + var type = mapType(gl, attribData.type); + + attributes[attribData.name] = { + type:type, + size:mapSize(type), + location:gl.getAttribLocation(program, attribData.name), + //TODO - make an attribute object + pointer: pointer + }; + } + + return attributes; +}; + +var pointer = function(type, normalized, stride, start){ + // console.log(this.location) + gl.vertexAttribPointer(this.location,this.size, type || gl.FLOAT, normalized || false, stride || 0, start || 0); +}; + +module.exports = extractAttributes; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js new file mode 100644 index 0000000..78d9f0d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js @@ -0,0 +1,35 @@ +var mapType = require('./mapType'); +var defaultValue = require('./defaultValue'); + +/** + * Extracts the uniforms + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the uniforms from + * @return uniforms {Object} + */ +var extractUniforms = function(gl, program) +{ + var uniforms = {}; + + var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + + for (var i = 0; i < totalUniforms; i++) + { + var uniformData = gl.getActiveUniform(program, i); + var name = uniformData.name.replace(/\[.*?\]/, ""); + var type = mapType(gl, uniformData.type ); + + uniforms[name] = { + type:type, + size:uniformData.size, + location:gl.getUniformLocation(program, name), + value:defaultValue(type, uniformData.size) + }; + } + + return uniforms; +}; + +module.exports = extractUniforms; diff --git a/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js new file mode 100644 index 0000000..c2aa597 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js @@ -0,0 +1,144 @@ +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param uniforms {Array} @mat ? + * @return attributes {Object} + */ +var generateUniformAccessObject = function(gl, uniformData) +{ + // this is the object we will be sending back. + // an object hierachy will be created for structs + var uniforms = {data:{}}; + + uniforms.gl = gl; + + var uniformKeys= Object.keys(uniformData); + + for (var i = 0; i < uniformKeys.length; i++) + { + var fullName = uniformKeys[i]; + + var nameTokens = fullName.split('.'); + var name = nameTokens[nameTokens.length - 1]; + + + var uniformGroup = getUniformGroup(nameTokens, uniforms); + + var uniform = uniformData[fullName]; + uniformGroup.data[name] = uniform; + + uniformGroup.gl = gl; + + Object.defineProperty(uniformGroup, name, { + get: generateGetter(name), + set: generateSetter(name, uniform) + }); + } + + return uniforms; +}; + +var generateGetter = function(name) +{ + var template = getterTemplate.replace('%%', name); + return new Function(template); // jshint ignore:line +}; + +var generateSetter = function(name, uniform) +{ + var template = setterTemplate.replace(/%%/g, name); + var setTemplate; + + if(uniform.size === 1) + { + setTemplate = GLSL_TO_SINGLE_SETTERS[uniform.type]; + } + else + { + setTemplate = GLSL_TO_ARRAY_SETTERS[uniform.type]; + } + + if(setTemplate) + { + template += "\nthis.gl." + setTemplate + ";"; + } + + return new Function('value', template); // jshint ignore:line +}; + +var getUniformGroup = function(nameTokens, uniform) +{ + var cur = uniform; + + for (var i = 0; i < nameTokens.length - 1; i++) + { + var o = cur[nameTokens[i]] || {data:{}}; + cur[nameTokens[i]] = o; + cur = o; + } + + return cur; +}; + +var getterTemplate = [ + 'return this.data.%%.value;', +].join('\n'); + +var setterTemplate = [ + 'this.data.%%.value = value;', + 'var location = this.data.%%.location;' +].join('\n'); + + +var GLSL_TO_SINGLE_SETTERS = { + + 'float': 'uniform1f(location, value)', + + 'vec2': 'uniform2f(location, value[0], value[1])', + 'vec3': 'uniform3f(location, value[0], value[1], value[2])', + 'vec4': 'uniform4f(location, value[0], value[1], value[2], value[3])', + + 'int': 'uniform1i(location, value)', + 'ivec2': 'uniform2i(location, value[0], value[1])', + 'ivec3': 'uniform3i(location, value[0], value[1], value[2])', + 'ivec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'bool': 'uniform1i(location, value)', + 'bvec2': 'uniform2i(location, value[0], value[1])', + 'bvec3': 'uniform3i(location, value[0], value[1], value[2])', + 'bvec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'mat2': 'uniformMatrix2fv(location, false, value)', + 'mat3': 'uniformMatrix3fv(location, false, value)', + 'mat4': 'uniformMatrix4fv(location, false, value)', + + 'sampler2D':'uniform1i(location, value)', + 'samplerCube': 'uniform1i(location, value)' + +}; + +var GLSL_TO_ARRAY_SETTERS = { + + 'float': 'uniform1fv(location, value)', + + 'vec2': 'uniform2fv(location, value)', + 'vec3': 'uniform3fv(location, value)', + 'vec4': 'uniform4fv(location, value)', + + 'int': 'uniform1iv(location, value)', + 'ivec2': 'uniform2iv(location, value)', + 'ivec3': 'uniform3iv(location, value)', + 'ivec4': 'uniform4iv(location, value)', + + 'bool': 'uniform1iv(location, value)', + 'bvec2': 'uniform2iv(location, value)', + 'bvec3': 'uniform3iv(location, value)', + 'bvec4': 'uniform4iv(location, value)', + + 'sampler2D':'uniform1iv(location, value)', + 'samplerCube': 'uniform1i(location, value)' +}; + +module.exports = generateUniformAccessObject; diff --git a/src/core/renderers/webgl/systems/shader/shader/index.js b/src/core/renderers/webgl/systems/shader/shader/index.js new file mode 100644 index 0000000..e08684d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/index.js @@ -0,0 +1,10 @@ +module.exports = { + compileProgram: require('./compileProgram'), + defaultValue: require('./defaultValue'), + extractAttributes: require('./extractAttributes'), + extractUniforms: require('./extractUniforms'), + generateUniformAccessObject: require('./generateUniformAccessObject'), + setPrecision: require('./setPrecision'), + mapSize: require('./mapSize'), + mapType: require('./mapType') +}; \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/shader/shader/mapSize.js b/src/core/renderers/webgl/systems/shader/shader/mapSize.js new file mode 100644 index 0000000..61d9b36 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapSize.js @@ -0,0 +1,36 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +var mapSize = function(type) +{ + return GLSL_TO_SIZE[type]; +}; + + +var GLSL_TO_SIZE = { + 'float': 1, + 'vec2': 2, + 'vec3': 3, + 'vec4': 4, + + 'int': 1, + 'ivec2': 2, + 'ivec3': 3, + 'ivec4': 4, + + 'bool': 1, + 'bvec2': 2, + 'bvec3': 3, + 'bvec4': 4, + + 'mat2': 4, + 'mat3': 9, + 'mat4': 16, + + 'sampler2D': 1 +}; + +module.exports = mapSize; diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js new file mode 100644 index 0000000..313f078 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js @@ -0,0 +1,41 @@ + +var mapType = require('./mapType'); +var mapSize = require('./mapSize'); + +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the attributes from + * @return attributes {Object} + */ +var extractAttributes = function(gl, program) +{ + var attributes = {}; + + var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + + for (var i = 0; i < totalAttributes; i++) + { + var attribData = gl.getActiveAttrib(program, i); + var type = mapType(gl, attribData.type); + + attributes[attribData.name] = { + type:type, + size:mapSize(type), + location:gl.getAttribLocation(program, attribData.name), + //TODO - make an attribute object + pointer: pointer + }; + } + + return attributes; +}; + +var pointer = function(type, normalized, stride, start){ + // console.log(this.location) + gl.vertexAttribPointer(this.location,this.size, type || gl.FLOAT, normalized || false, stride || 0, start || 0); +}; + +module.exports = extractAttributes; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js new file mode 100644 index 0000000..78d9f0d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js @@ -0,0 +1,35 @@ +var mapType = require('./mapType'); +var defaultValue = require('./defaultValue'); + +/** + * Extracts the uniforms + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the uniforms from + * @return uniforms {Object} + */ +var extractUniforms = function(gl, program) +{ + var uniforms = {}; + + var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + + for (var i = 0; i < totalUniforms; i++) + { + var uniformData = gl.getActiveUniform(program, i); + var name = uniformData.name.replace(/\[.*?\]/, ""); + var type = mapType(gl, uniformData.type ); + + uniforms[name] = { + type:type, + size:uniformData.size, + location:gl.getUniformLocation(program, name), + value:defaultValue(type, uniformData.size) + }; + } + + return uniforms; +}; + +module.exports = extractUniforms; diff --git a/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js new file mode 100644 index 0000000..c2aa597 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js @@ -0,0 +1,144 @@ +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param uniforms {Array} @mat ? + * @return attributes {Object} + */ +var generateUniformAccessObject = function(gl, uniformData) +{ + // this is the object we will be sending back. + // an object hierachy will be created for structs + var uniforms = {data:{}}; + + uniforms.gl = gl; + + var uniformKeys= Object.keys(uniformData); + + for (var i = 0; i < uniformKeys.length; i++) + { + var fullName = uniformKeys[i]; + + var nameTokens = fullName.split('.'); + var name = nameTokens[nameTokens.length - 1]; + + + var uniformGroup = getUniformGroup(nameTokens, uniforms); + + var uniform = uniformData[fullName]; + uniformGroup.data[name] = uniform; + + uniformGroup.gl = gl; + + Object.defineProperty(uniformGroup, name, { + get: generateGetter(name), + set: generateSetter(name, uniform) + }); + } + + return uniforms; +}; + +var generateGetter = function(name) +{ + var template = getterTemplate.replace('%%', name); + return new Function(template); // jshint ignore:line +}; + +var generateSetter = function(name, uniform) +{ + var template = setterTemplate.replace(/%%/g, name); + var setTemplate; + + if(uniform.size === 1) + { + setTemplate = GLSL_TO_SINGLE_SETTERS[uniform.type]; + } + else + { + setTemplate = GLSL_TO_ARRAY_SETTERS[uniform.type]; + } + + if(setTemplate) + { + template += "\nthis.gl." + setTemplate + ";"; + } + + return new Function('value', template); // jshint ignore:line +}; + +var getUniformGroup = function(nameTokens, uniform) +{ + var cur = uniform; + + for (var i = 0; i < nameTokens.length - 1; i++) + { + var o = cur[nameTokens[i]] || {data:{}}; + cur[nameTokens[i]] = o; + cur = o; + } + + return cur; +}; + +var getterTemplate = [ + 'return this.data.%%.value;', +].join('\n'); + +var setterTemplate = [ + 'this.data.%%.value = value;', + 'var location = this.data.%%.location;' +].join('\n'); + + +var GLSL_TO_SINGLE_SETTERS = { + + 'float': 'uniform1f(location, value)', + + 'vec2': 'uniform2f(location, value[0], value[1])', + 'vec3': 'uniform3f(location, value[0], value[1], value[2])', + 'vec4': 'uniform4f(location, value[0], value[1], value[2], value[3])', + + 'int': 'uniform1i(location, value)', + 'ivec2': 'uniform2i(location, value[0], value[1])', + 'ivec3': 'uniform3i(location, value[0], value[1], value[2])', + 'ivec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'bool': 'uniform1i(location, value)', + 'bvec2': 'uniform2i(location, value[0], value[1])', + 'bvec3': 'uniform3i(location, value[0], value[1], value[2])', + 'bvec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'mat2': 'uniformMatrix2fv(location, false, value)', + 'mat3': 'uniformMatrix3fv(location, false, value)', + 'mat4': 'uniformMatrix4fv(location, false, value)', + + 'sampler2D':'uniform1i(location, value)', + 'samplerCube': 'uniform1i(location, value)' + +}; + +var GLSL_TO_ARRAY_SETTERS = { + + 'float': 'uniform1fv(location, value)', + + 'vec2': 'uniform2fv(location, value)', + 'vec3': 'uniform3fv(location, value)', + 'vec4': 'uniform4fv(location, value)', + + 'int': 'uniform1iv(location, value)', + 'ivec2': 'uniform2iv(location, value)', + 'ivec3': 'uniform3iv(location, value)', + 'ivec4': 'uniform4iv(location, value)', + + 'bool': 'uniform1iv(location, value)', + 'bvec2': 'uniform2iv(location, value)', + 'bvec3': 'uniform3iv(location, value)', + 'bvec4': 'uniform4iv(location, value)', + + 'sampler2D':'uniform1iv(location, value)', + 'samplerCube': 'uniform1i(location, value)' +}; + +module.exports = generateUniformAccessObject; diff --git a/src/core/renderers/webgl/systems/shader/shader/index.js b/src/core/renderers/webgl/systems/shader/shader/index.js new file mode 100644 index 0000000..e08684d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/index.js @@ -0,0 +1,10 @@ +module.exports = { + compileProgram: require('./compileProgram'), + defaultValue: require('./defaultValue'), + extractAttributes: require('./extractAttributes'), + extractUniforms: require('./extractUniforms'), + generateUniformAccessObject: require('./generateUniformAccessObject'), + setPrecision: require('./setPrecision'), + mapSize: require('./mapSize'), + mapType: require('./mapType') +}; \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/shader/shader/mapSize.js b/src/core/renderers/webgl/systems/shader/shader/mapSize.js new file mode 100644 index 0000000..61d9b36 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapSize.js @@ -0,0 +1,36 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +var mapSize = function(type) +{ + return GLSL_TO_SIZE[type]; +}; + + +var GLSL_TO_SIZE = { + 'float': 1, + 'vec2': 2, + 'vec3': 3, + 'vec4': 4, + + 'int': 1, + 'ivec2': 2, + 'ivec3': 3, + 'ivec4': 4, + + 'bool': 1, + 'bvec2': 2, + 'bvec3': 3, + 'bvec4': 4, + + 'mat2': 4, + 'mat3': 9, + 'mat4': 16, + + 'sampler2D': 1 +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js new file mode 100644 index 0000000..581dace --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -0,0 +1,47 @@ + + +var mapSize = function(gl, type) +{ + if(!GL_TABLE) + { + var typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for(var i = 0; i < typeNames.length; ++i) + { + var tn = typeNames[i]; + GL_TABLE[ gl[tn] ] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +}; + +var GL_TABLE = null; + +var GL_TO_GLSL_TYPES = { + 'FLOAT': 'float', + 'FLOAT_VEC2': 'vec2', + 'FLOAT_VEC3': 'vec3', + 'FLOAT_VEC4': 'vec4', + + 'INT': 'int', + 'INT_VEC2': 'ivec2', + 'INT_VEC3': 'ivec3', + 'INT_VEC4': 'ivec4', + + 'BOOL': 'bool', + 'BOOL_VEC2': 'bvec2', + 'BOOL_VEC3': 'bvec3', + 'BOOL_VEC4': 'bvec4', + + 'FLOAT_MAT2': 'mat2', + 'FLOAT_MAT3': 'mat3', + 'FLOAT_MAT4': 'mat4', + + 'SAMPLER_2D': 'sampler2D', + 'SAMPLER_CUBE': 'samplerCube' +}; + +module.exports = mapSize; diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js new file mode 100644 index 0000000..313f078 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js @@ -0,0 +1,41 @@ + +var mapType = require('./mapType'); +var mapSize = require('./mapSize'); + +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the attributes from + * @return attributes {Object} + */ +var extractAttributes = function(gl, program) +{ + var attributes = {}; + + var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + + for (var i = 0; i < totalAttributes; i++) + { + var attribData = gl.getActiveAttrib(program, i); + var type = mapType(gl, attribData.type); + + attributes[attribData.name] = { + type:type, + size:mapSize(type), + location:gl.getAttribLocation(program, attribData.name), + //TODO - make an attribute object + pointer: pointer + }; + } + + return attributes; +}; + +var pointer = function(type, normalized, stride, start){ + // console.log(this.location) + gl.vertexAttribPointer(this.location,this.size, type || gl.FLOAT, normalized || false, stride || 0, start || 0); +}; + +module.exports = extractAttributes; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js new file mode 100644 index 0000000..78d9f0d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js @@ -0,0 +1,35 @@ +var mapType = require('./mapType'); +var defaultValue = require('./defaultValue'); + +/** + * Extracts the uniforms + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the uniforms from + * @return uniforms {Object} + */ +var extractUniforms = function(gl, program) +{ + var uniforms = {}; + + var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + + for (var i = 0; i < totalUniforms; i++) + { + var uniformData = gl.getActiveUniform(program, i); + var name = uniformData.name.replace(/\[.*?\]/, ""); + var type = mapType(gl, uniformData.type ); + + uniforms[name] = { + type:type, + size:uniformData.size, + location:gl.getUniformLocation(program, name), + value:defaultValue(type, uniformData.size) + }; + } + + return uniforms; +}; + +module.exports = extractUniforms; diff --git a/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js new file mode 100644 index 0000000..c2aa597 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js @@ -0,0 +1,144 @@ +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param uniforms {Array} @mat ? + * @return attributes {Object} + */ +var generateUniformAccessObject = function(gl, uniformData) +{ + // this is the object we will be sending back. + // an object hierachy will be created for structs + var uniforms = {data:{}}; + + uniforms.gl = gl; + + var uniformKeys= Object.keys(uniformData); + + for (var i = 0; i < uniformKeys.length; i++) + { + var fullName = uniformKeys[i]; + + var nameTokens = fullName.split('.'); + var name = nameTokens[nameTokens.length - 1]; + + + var uniformGroup = getUniformGroup(nameTokens, uniforms); + + var uniform = uniformData[fullName]; + uniformGroup.data[name] = uniform; + + uniformGroup.gl = gl; + + Object.defineProperty(uniformGroup, name, { + get: generateGetter(name), + set: generateSetter(name, uniform) + }); + } + + return uniforms; +}; + +var generateGetter = function(name) +{ + var template = getterTemplate.replace('%%', name); + return new Function(template); // jshint ignore:line +}; + +var generateSetter = function(name, uniform) +{ + var template = setterTemplate.replace(/%%/g, name); + var setTemplate; + + if(uniform.size === 1) + { + setTemplate = GLSL_TO_SINGLE_SETTERS[uniform.type]; + } + else + { + setTemplate = GLSL_TO_ARRAY_SETTERS[uniform.type]; + } + + if(setTemplate) + { + template += "\nthis.gl." + setTemplate + ";"; + } + + return new Function('value', template); // jshint ignore:line +}; + +var getUniformGroup = function(nameTokens, uniform) +{ + var cur = uniform; + + for (var i = 0; i < nameTokens.length - 1; i++) + { + var o = cur[nameTokens[i]] || {data:{}}; + cur[nameTokens[i]] = o; + cur = o; + } + + return cur; +}; + +var getterTemplate = [ + 'return this.data.%%.value;', +].join('\n'); + +var setterTemplate = [ + 'this.data.%%.value = value;', + 'var location = this.data.%%.location;' +].join('\n'); + + +var GLSL_TO_SINGLE_SETTERS = { + + 'float': 'uniform1f(location, value)', + + 'vec2': 'uniform2f(location, value[0], value[1])', + 'vec3': 'uniform3f(location, value[0], value[1], value[2])', + 'vec4': 'uniform4f(location, value[0], value[1], value[2], value[3])', + + 'int': 'uniform1i(location, value)', + 'ivec2': 'uniform2i(location, value[0], value[1])', + 'ivec3': 'uniform3i(location, value[0], value[1], value[2])', + 'ivec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'bool': 'uniform1i(location, value)', + 'bvec2': 'uniform2i(location, value[0], value[1])', + 'bvec3': 'uniform3i(location, value[0], value[1], value[2])', + 'bvec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'mat2': 'uniformMatrix2fv(location, false, value)', + 'mat3': 'uniformMatrix3fv(location, false, value)', + 'mat4': 'uniformMatrix4fv(location, false, value)', + + 'sampler2D':'uniform1i(location, value)', + 'samplerCube': 'uniform1i(location, value)' + +}; + +var GLSL_TO_ARRAY_SETTERS = { + + 'float': 'uniform1fv(location, value)', + + 'vec2': 'uniform2fv(location, value)', + 'vec3': 'uniform3fv(location, value)', + 'vec4': 'uniform4fv(location, value)', + + 'int': 'uniform1iv(location, value)', + 'ivec2': 'uniform2iv(location, value)', + 'ivec3': 'uniform3iv(location, value)', + 'ivec4': 'uniform4iv(location, value)', + + 'bool': 'uniform1iv(location, value)', + 'bvec2': 'uniform2iv(location, value)', + 'bvec3': 'uniform3iv(location, value)', + 'bvec4': 'uniform4iv(location, value)', + + 'sampler2D':'uniform1iv(location, value)', + 'samplerCube': 'uniform1i(location, value)' +}; + +module.exports = generateUniformAccessObject; diff --git a/src/core/renderers/webgl/systems/shader/shader/index.js b/src/core/renderers/webgl/systems/shader/shader/index.js new file mode 100644 index 0000000..e08684d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/index.js @@ -0,0 +1,10 @@ +module.exports = { + compileProgram: require('./compileProgram'), + defaultValue: require('./defaultValue'), + extractAttributes: require('./extractAttributes'), + extractUniforms: require('./extractUniforms'), + generateUniformAccessObject: require('./generateUniformAccessObject'), + setPrecision: require('./setPrecision'), + mapSize: require('./mapSize'), + mapType: require('./mapType') +}; \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/shader/shader/mapSize.js b/src/core/renderers/webgl/systems/shader/shader/mapSize.js new file mode 100644 index 0000000..61d9b36 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapSize.js @@ -0,0 +1,36 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +var mapSize = function(type) +{ + return GLSL_TO_SIZE[type]; +}; + + +var GLSL_TO_SIZE = { + 'float': 1, + 'vec2': 2, + 'vec3': 3, + 'vec4': 4, + + 'int': 1, + 'ivec2': 2, + 'ivec3': 3, + 'ivec4': 4, + + 'bool': 1, + 'bvec2': 2, + 'bvec3': 3, + 'bvec4': 4, + + 'mat2': 4, + 'mat3': 9, + 'mat4': 16, + + 'sampler2D': 1 +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js new file mode 100644 index 0000000..581dace --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -0,0 +1,47 @@ + + +var mapSize = function(gl, type) +{ + if(!GL_TABLE) + { + var typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for(var i = 0; i < typeNames.length; ++i) + { + var tn = typeNames[i]; + GL_TABLE[ gl[tn] ] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +}; + +var GL_TABLE = null; + +var GL_TO_GLSL_TYPES = { + 'FLOAT': 'float', + 'FLOAT_VEC2': 'vec2', + 'FLOAT_VEC3': 'vec3', + 'FLOAT_VEC4': 'vec4', + + 'INT': 'int', + 'INT_VEC2': 'ivec2', + 'INT_VEC3': 'ivec3', + 'INT_VEC4': 'ivec4', + + 'BOOL': 'bool', + 'BOOL_VEC2': 'bvec2', + 'BOOL_VEC3': 'bvec3', + 'BOOL_VEC4': 'bvec4', + + 'FLOAT_MAT2': 'mat2', + 'FLOAT_MAT3': 'mat3', + 'FLOAT_MAT4': 'mat4', + + 'SAMPLER_2D': 'sampler2D', + 'SAMPLER_CUBE': 'samplerCube' +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/setPrecision.js b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js new file mode 100644 index 0000000..383459a --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +var setPrecision = function(src, precision) +{ + if(src.substring(0, 9) !== 'precision') + { + return 'precision ' + precision + ' float;\n' + src; + } + + return src; +}; + +module.exports = setPrecision; diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js new file mode 100644 index 0000000..313f078 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js @@ -0,0 +1,41 @@ + +var mapType = require('./mapType'); +var mapSize = require('./mapSize'); + +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the attributes from + * @return attributes {Object} + */ +var extractAttributes = function(gl, program) +{ + var attributes = {}; + + var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + + for (var i = 0; i < totalAttributes; i++) + { + var attribData = gl.getActiveAttrib(program, i); + var type = mapType(gl, attribData.type); + + attributes[attribData.name] = { + type:type, + size:mapSize(type), + location:gl.getAttribLocation(program, attribData.name), + //TODO - make an attribute object + pointer: pointer + }; + } + + return attributes; +}; + +var pointer = function(type, normalized, stride, start){ + // console.log(this.location) + gl.vertexAttribPointer(this.location,this.size, type || gl.FLOAT, normalized || false, stride || 0, start || 0); +}; + +module.exports = extractAttributes; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js new file mode 100644 index 0000000..78d9f0d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js @@ -0,0 +1,35 @@ +var mapType = require('./mapType'); +var defaultValue = require('./defaultValue'); + +/** + * Extracts the uniforms + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the uniforms from + * @return uniforms {Object} + */ +var extractUniforms = function(gl, program) +{ + var uniforms = {}; + + var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + + for (var i = 0; i < totalUniforms; i++) + { + var uniformData = gl.getActiveUniform(program, i); + var name = uniformData.name.replace(/\[.*?\]/, ""); + var type = mapType(gl, uniformData.type ); + + uniforms[name] = { + type:type, + size:uniformData.size, + location:gl.getUniformLocation(program, name), + value:defaultValue(type, uniformData.size) + }; + } + + return uniforms; +}; + +module.exports = extractUniforms; diff --git a/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js new file mode 100644 index 0000000..c2aa597 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js @@ -0,0 +1,144 @@ +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param uniforms {Array} @mat ? + * @return attributes {Object} + */ +var generateUniformAccessObject = function(gl, uniformData) +{ + // this is the object we will be sending back. + // an object hierachy will be created for structs + var uniforms = {data:{}}; + + uniforms.gl = gl; + + var uniformKeys= Object.keys(uniformData); + + for (var i = 0; i < uniformKeys.length; i++) + { + var fullName = uniformKeys[i]; + + var nameTokens = fullName.split('.'); + var name = nameTokens[nameTokens.length - 1]; + + + var uniformGroup = getUniformGroup(nameTokens, uniforms); + + var uniform = uniformData[fullName]; + uniformGroup.data[name] = uniform; + + uniformGroup.gl = gl; + + Object.defineProperty(uniformGroup, name, { + get: generateGetter(name), + set: generateSetter(name, uniform) + }); + } + + return uniforms; +}; + +var generateGetter = function(name) +{ + var template = getterTemplate.replace('%%', name); + return new Function(template); // jshint ignore:line +}; + +var generateSetter = function(name, uniform) +{ + var template = setterTemplate.replace(/%%/g, name); + var setTemplate; + + if(uniform.size === 1) + { + setTemplate = GLSL_TO_SINGLE_SETTERS[uniform.type]; + } + else + { + setTemplate = GLSL_TO_ARRAY_SETTERS[uniform.type]; + } + + if(setTemplate) + { + template += "\nthis.gl." + setTemplate + ";"; + } + + return new Function('value', template); // jshint ignore:line +}; + +var getUniformGroup = function(nameTokens, uniform) +{ + var cur = uniform; + + for (var i = 0; i < nameTokens.length - 1; i++) + { + var o = cur[nameTokens[i]] || {data:{}}; + cur[nameTokens[i]] = o; + cur = o; + } + + return cur; +}; + +var getterTemplate = [ + 'return this.data.%%.value;', +].join('\n'); + +var setterTemplate = [ + 'this.data.%%.value = value;', + 'var location = this.data.%%.location;' +].join('\n'); + + +var GLSL_TO_SINGLE_SETTERS = { + + 'float': 'uniform1f(location, value)', + + 'vec2': 'uniform2f(location, value[0], value[1])', + 'vec3': 'uniform3f(location, value[0], value[1], value[2])', + 'vec4': 'uniform4f(location, value[0], value[1], value[2], value[3])', + + 'int': 'uniform1i(location, value)', + 'ivec2': 'uniform2i(location, value[0], value[1])', + 'ivec3': 'uniform3i(location, value[0], value[1], value[2])', + 'ivec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'bool': 'uniform1i(location, value)', + 'bvec2': 'uniform2i(location, value[0], value[1])', + 'bvec3': 'uniform3i(location, value[0], value[1], value[2])', + 'bvec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'mat2': 'uniformMatrix2fv(location, false, value)', + 'mat3': 'uniformMatrix3fv(location, false, value)', + 'mat4': 'uniformMatrix4fv(location, false, value)', + + 'sampler2D':'uniform1i(location, value)', + 'samplerCube': 'uniform1i(location, value)' + +}; + +var GLSL_TO_ARRAY_SETTERS = { + + 'float': 'uniform1fv(location, value)', + + 'vec2': 'uniform2fv(location, value)', + 'vec3': 'uniform3fv(location, value)', + 'vec4': 'uniform4fv(location, value)', + + 'int': 'uniform1iv(location, value)', + 'ivec2': 'uniform2iv(location, value)', + 'ivec3': 'uniform3iv(location, value)', + 'ivec4': 'uniform4iv(location, value)', + + 'bool': 'uniform1iv(location, value)', + 'bvec2': 'uniform2iv(location, value)', + 'bvec3': 'uniform3iv(location, value)', + 'bvec4': 'uniform4iv(location, value)', + + 'sampler2D':'uniform1iv(location, value)', + 'samplerCube': 'uniform1i(location, value)' +}; + +module.exports = generateUniformAccessObject; diff --git a/src/core/renderers/webgl/systems/shader/shader/index.js b/src/core/renderers/webgl/systems/shader/shader/index.js new file mode 100644 index 0000000..e08684d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/index.js @@ -0,0 +1,10 @@ +module.exports = { + compileProgram: require('./compileProgram'), + defaultValue: require('./defaultValue'), + extractAttributes: require('./extractAttributes'), + extractUniforms: require('./extractUniforms'), + generateUniformAccessObject: require('./generateUniformAccessObject'), + setPrecision: require('./setPrecision'), + mapSize: require('./mapSize'), + mapType: require('./mapType') +}; \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/shader/shader/mapSize.js b/src/core/renderers/webgl/systems/shader/shader/mapSize.js new file mode 100644 index 0000000..61d9b36 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapSize.js @@ -0,0 +1,36 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +var mapSize = function(type) +{ + return GLSL_TO_SIZE[type]; +}; + + +var GLSL_TO_SIZE = { + 'float': 1, + 'vec2': 2, + 'vec3': 3, + 'vec4': 4, + + 'int': 1, + 'ivec2': 2, + 'ivec3': 3, + 'ivec4': 4, + + 'bool': 1, + 'bvec2': 2, + 'bvec3': 3, + 'bvec4': 4, + + 'mat2': 4, + 'mat3': 9, + 'mat4': 16, + + 'sampler2D': 1 +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js new file mode 100644 index 0000000..581dace --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -0,0 +1,47 @@ + + +var mapSize = function(gl, type) +{ + if(!GL_TABLE) + { + var typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for(var i = 0; i < typeNames.length; ++i) + { + var tn = typeNames[i]; + GL_TABLE[ gl[tn] ] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +}; + +var GL_TABLE = null; + +var GL_TO_GLSL_TYPES = { + 'FLOAT': 'float', + 'FLOAT_VEC2': 'vec2', + 'FLOAT_VEC3': 'vec3', + 'FLOAT_VEC4': 'vec4', + + 'INT': 'int', + 'INT_VEC2': 'ivec2', + 'INT_VEC3': 'ivec3', + 'INT_VEC4': 'ivec4', + + 'BOOL': 'bool', + 'BOOL_VEC2': 'bvec2', + 'BOOL_VEC3': 'bvec3', + 'BOOL_VEC4': 'bvec4', + + 'FLOAT_MAT2': 'mat2', + 'FLOAT_MAT3': 'mat3', + 'FLOAT_MAT4': 'mat4', + + 'SAMPLER_2D': 'sampler2D', + 'SAMPLER_CUBE': 'samplerCube' +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/setPrecision.js b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js new file mode 100644 index 0000000..383459a --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +var setPrecision = function(src, precision) +{ + if(src.substring(0, 9) !== 'precision') + { + return 'precision ' + precision + ' float;\n' + src; + } + + return src; +}; + +module.exports = setPrecision; diff --git a/src/core/renderers/webgl/systems/textures/GLTexture.js b/src/core/renderers/webgl/systems/textures/GLTexture.js new file mode 100644 index 0000000..cb8f6ad --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/GLTexture.js @@ -0,0 +1,333 @@ + +/** + * Helper class to create a WebGL Texture + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL context + * @param width {number} the width of the texture + * @param height {number} the height of the texture + * @param format {number} the pixel format of the texture. defaults to gl.RGBA + * @param type {number} the gl type of the texture. defaults to gl.UNSIGNED_BYTE + */ +var Texture = function(gl, width, height, format, type) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + + /** + * The WebGL texture + * + * @member {WebGLTexture} + */ + this.texture = gl.createTexture(); + + /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() + * + * @member {Boolean} + */ + // some settings.. + this.mipmap = false; + + + /** + * Set to true to enable pre-multiplied alpha + * + * @member {Boolean} + */ + this.premultiplyAlpha = false; + + /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || gl.RGBA; + + /** + * The gl type of the texture. defaults to gl.UNSIGNED_BYTE + * + * @member {Number} + */ + this.type = type || gl.UNSIGNED_BYTE; + + +}; + +/** + * Uploads this texture to the GPU + * @param source {HTMLImageElement|ImageData|HTMLVideoElement} the source image of the texture + */ +Texture.prototype.upload = function(source) +{ + this.bind(); + + var gl = this.gl; + + + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + var newWidth = source.videoWidth || source.width; + var newHeight = source.videoHeight || source.height; + + if(newHeight !== this.height || newWidth !== this.width) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.format, this.type, source); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.format, this.type, source); + } + + // if the source is a video, we need to use the videoWidth / videoHeight properties as width / height will be incorrect. + this.width = newWidth; + this.height = newHeight; + +}; + +var FLOATING_POINT_AVAILABLE = false; + +/** + * Use a data source and uploads this texture to the GPU + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.prototype.uploadData = function(data, width, height) +{ + this.bind(); + + var gl = this.gl; + + + if(data instanceof Float32Array) + { + if(!FLOATING_POINT_AVAILABLE) + { + var ext = gl.getExtension("OES_texture_float"); + + if(ext) + { + FLOATING_POINT_AVAILABLE = true; + } + else + { + throw new Error('floating point textures not available'); + } + } + + this.type = gl.FLOAT; + } + else + { + // TODO support for other types + this.type = this.type || gl.UNSIGNED_BYTE; + } + + // what type of data? + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + + if(width !== this.width || height !== this.height) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, this.type, data || null); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, this.format, this.type, data || null); + } + + this.width = width; + this.height = height; + + +// texSubImage2D +}; + +/** + * Binds the texture + * @param location + */ +Texture.prototype.bind = function(location) +{ + var gl = this.gl; + + if(location !== undefined) + { + gl.activeTexture(gl.TEXTURE0 + location); + } + + gl.bindTexture(gl.TEXTURE_2D, this.texture); +}; + +/** + * Unbinds the texture + */ +Texture.prototype.unbind = function() +{ + var gl = this.gl; + gl.bindTexture(gl.TEXTURE_2D, null); +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.minFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + if(this.mipmap) + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR : gl.NEAREST); + } +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.magFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, linear ? gl.LINEAR : gl.NEAREST); +}; + +/** + * Enables mipmapping + */ +Texture.prototype.enableMipmap = function() +{ + var gl = this.gl; + + this.bind(); + + this.mipmap = true; + + gl.generateMipmap(gl.TEXTURE_2D); +}; + +/** + * Enables linear filtering + */ +Texture.prototype.enableLinearScaling = function() +{ + this.minFilter(true); + this.magFilter(true); +}; + +/** + * Enables nearest neighbour interpolation + */ +Texture.prototype.enableNearestScaling = function() +{ + this.minFilter(false); + this.magFilter(false); +}; + +/** + * Enables clamping on the texture so WebGL will not repeat it + */ +Texture.prototype.enableWrapClamp = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); +}; + +/** + * Enable tiling on the texture + */ +Texture.prototype.enableWrapRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); +}; + +Texture.prototype.enableWrapMirrorRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT); +}; + + +/** + * Destroys this texture + */ +Texture.prototype.destroy = function() +{ + var gl = this.gl; + //TODO + gl.deleteTexture(this.texture); +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param source {HTMLImageElement|ImageData} the source image of the texture + * @param premultiplyAlpha {Boolean} If we want to use pre-multiplied alpha + */ +Texture.fromSource = function(gl, source, premultiplyAlpha) +{ + var texture = new Texture(gl); + texture.premultiplyAlpha = premultiplyAlpha || false; + texture.upload(source); + + return texture; +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.fromData = function(gl, data, width, height) +{ + //console.log(data, width, height); + var texture = new Texture(gl); + texture.uploadData(data, width, height); + + return texture; +}; + + +module.exports = Texture; diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js new file mode 100644 index 0000000..313f078 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js @@ -0,0 +1,41 @@ + +var mapType = require('./mapType'); +var mapSize = require('./mapSize'); + +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the attributes from + * @return attributes {Object} + */ +var extractAttributes = function(gl, program) +{ + var attributes = {}; + + var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + + for (var i = 0; i < totalAttributes; i++) + { + var attribData = gl.getActiveAttrib(program, i); + var type = mapType(gl, attribData.type); + + attributes[attribData.name] = { + type:type, + size:mapSize(type), + location:gl.getAttribLocation(program, attribData.name), + //TODO - make an attribute object + pointer: pointer + }; + } + + return attributes; +}; + +var pointer = function(type, normalized, stride, start){ + // console.log(this.location) + gl.vertexAttribPointer(this.location,this.size, type || gl.FLOAT, normalized || false, stride || 0, start || 0); +}; + +module.exports = extractAttributes; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js new file mode 100644 index 0000000..78d9f0d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js @@ -0,0 +1,35 @@ +var mapType = require('./mapType'); +var defaultValue = require('./defaultValue'); + +/** + * Extracts the uniforms + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the uniforms from + * @return uniforms {Object} + */ +var extractUniforms = function(gl, program) +{ + var uniforms = {}; + + var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + + for (var i = 0; i < totalUniforms; i++) + { + var uniformData = gl.getActiveUniform(program, i); + var name = uniformData.name.replace(/\[.*?\]/, ""); + var type = mapType(gl, uniformData.type ); + + uniforms[name] = { + type:type, + size:uniformData.size, + location:gl.getUniformLocation(program, name), + value:defaultValue(type, uniformData.size) + }; + } + + return uniforms; +}; + +module.exports = extractUniforms; diff --git a/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js new file mode 100644 index 0000000..c2aa597 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js @@ -0,0 +1,144 @@ +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param uniforms {Array} @mat ? + * @return attributes {Object} + */ +var generateUniformAccessObject = function(gl, uniformData) +{ + // this is the object we will be sending back. + // an object hierachy will be created for structs + var uniforms = {data:{}}; + + uniforms.gl = gl; + + var uniformKeys= Object.keys(uniformData); + + for (var i = 0; i < uniformKeys.length; i++) + { + var fullName = uniformKeys[i]; + + var nameTokens = fullName.split('.'); + var name = nameTokens[nameTokens.length - 1]; + + + var uniformGroup = getUniformGroup(nameTokens, uniforms); + + var uniform = uniformData[fullName]; + uniformGroup.data[name] = uniform; + + uniformGroup.gl = gl; + + Object.defineProperty(uniformGroup, name, { + get: generateGetter(name), + set: generateSetter(name, uniform) + }); + } + + return uniforms; +}; + +var generateGetter = function(name) +{ + var template = getterTemplate.replace('%%', name); + return new Function(template); // jshint ignore:line +}; + +var generateSetter = function(name, uniform) +{ + var template = setterTemplate.replace(/%%/g, name); + var setTemplate; + + if(uniform.size === 1) + { + setTemplate = GLSL_TO_SINGLE_SETTERS[uniform.type]; + } + else + { + setTemplate = GLSL_TO_ARRAY_SETTERS[uniform.type]; + } + + if(setTemplate) + { + template += "\nthis.gl." + setTemplate + ";"; + } + + return new Function('value', template); // jshint ignore:line +}; + +var getUniformGroup = function(nameTokens, uniform) +{ + var cur = uniform; + + for (var i = 0; i < nameTokens.length - 1; i++) + { + var o = cur[nameTokens[i]] || {data:{}}; + cur[nameTokens[i]] = o; + cur = o; + } + + return cur; +}; + +var getterTemplate = [ + 'return this.data.%%.value;', +].join('\n'); + +var setterTemplate = [ + 'this.data.%%.value = value;', + 'var location = this.data.%%.location;' +].join('\n'); + + +var GLSL_TO_SINGLE_SETTERS = { + + 'float': 'uniform1f(location, value)', + + 'vec2': 'uniform2f(location, value[0], value[1])', + 'vec3': 'uniform3f(location, value[0], value[1], value[2])', + 'vec4': 'uniform4f(location, value[0], value[1], value[2], value[3])', + + 'int': 'uniform1i(location, value)', + 'ivec2': 'uniform2i(location, value[0], value[1])', + 'ivec3': 'uniform3i(location, value[0], value[1], value[2])', + 'ivec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'bool': 'uniform1i(location, value)', + 'bvec2': 'uniform2i(location, value[0], value[1])', + 'bvec3': 'uniform3i(location, value[0], value[1], value[2])', + 'bvec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'mat2': 'uniformMatrix2fv(location, false, value)', + 'mat3': 'uniformMatrix3fv(location, false, value)', + 'mat4': 'uniformMatrix4fv(location, false, value)', + + 'sampler2D':'uniform1i(location, value)', + 'samplerCube': 'uniform1i(location, value)' + +}; + +var GLSL_TO_ARRAY_SETTERS = { + + 'float': 'uniform1fv(location, value)', + + 'vec2': 'uniform2fv(location, value)', + 'vec3': 'uniform3fv(location, value)', + 'vec4': 'uniform4fv(location, value)', + + 'int': 'uniform1iv(location, value)', + 'ivec2': 'uniform2iv(location, value)', + 'ivec3': 'uniform3iv(location, value)', + 'ivec4': 'uniform4iv(location, value)', + + 'bool': 'uniform1iv(location, value)', + 'bvec2': 'uniform2iv(location, value)', + 'bvec3': 'uniform3iv(location, value)', + 'bvec4': 'uniform4iv(location, value)', + + 'sampler2D':'uniform1iv(location, value)', + 'samplerCube': 'uniform1i(location, value)' +}; + +module.exports = generateUniformAccessObject; diff --git a/src/core/renderers/webgl/systems/shader/shader/index.js b/src/core/renderers/webgl/systems/shader/shader/index.js new file mode 100644 index 0000000..e08684d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/index.js @@ -0,0 +1,10 @@ +module.exports = { + compileProgram: require('./compileProgram'), + defaultValue: require('./defaultValue'), + extractAttributes: require('./extractAttributes'), + extractUniforms: require('./extractUniforms'), + generateUniformAccessObject: require('./generateUniformAccessObject'), + setPrecision: require('./setPrecision'), + mapSize: require('./mapSize'), + mapType: require('./mapType') +}; \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/shader/shader/mapSize.js b/src/core/renderers/webgl/systems/shader/shader/mapSize.js new file mode 100644 index 0000000..61d9b36 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapSize.js @@ -0,0 +1,36 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +var mapSize = function(type) +{ + return GLSL_TO_SIZE[type]; +}; + + +var GLSL_TO_SIZE = { + 'float': 1, + 'vec2': 2, + 'vec3': 3, + 'vec4': 4, + + 'int': 1, + 'ivec2': 2, + 'ivec3': 3, + 'ivec4': 4, + + 'bool': 1, + 'bvec2': 2, + 'bvec3': 3, + 'bvec4': 4, + + 'mat2': 4, + 'mat3': 9, + 'mat4': 16, + + 'sampler2D': 1 +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js new file mode 100644 index 0000000..581dace --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -0,0 +1,47 @@ + + +var mapSize = function(gl, type) +{ + if(!GL_TABLE) + { + var typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for(var i = 0; i < typeNames.length; ++i) + { + var tn = typeNames[i]; + GL_TABLE[ gl[tn] ] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +}; + +var GL_TABLE = null; + +var GL_TO_GLSL_TYPES = { + 'FLOAT': 'float', + 'FLOAT_VEC2': 'vec2', + 'FLOAT_VEC3': 'vec3', + 'FLOAT_VEC4': 'vec4', + + 'INT': 'int', + 'INT_VEC2': 'ivec2', + 'INT_VEC3': 'ivec3', + 'INT_VEC4': 'ivec4', + + 'BOOL': 'bool', + 'BOOL_VEC2': 'bvec2', + 'BOOL_VEC3': 'bvec3', + 'BOOL_VEC4': 'bvec4', + + 'FLOAT_MAT2': 'mat2', + 'FLOAT_MAT3': 'mat3', + 'FLOAT_MAT4': 'mat4', + + 'SAMPLER_2D': 'sampler2D', + 'SAMPLER_CUBE': 'samplerCube' +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/setPrecision.js b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js new file mode 100644 index 0000000..383459a --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +var setPrecision = function(src, precision) +{ + if(src.substring(0, 9) !== 'precision') + { + return 'precision ' + precision + ' float;\n' + src; + } + + return src; +}; + +module.exports = setPrecision; diff --git a/src/core/renderers/webgl/systems/textures/GLTexture.js b/src/core/renderers/webgl/systems/textures/GLTexture.js new file mode 100644 index 0000000..cb8f6ad --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/GLTexture.js @@ -0,0 +1,333 @@ + +/** + * Helper class to create a WebGL Texture + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL context + * @param width {number} the width of the texture + * @param height {number} the height of the texture + * @param format {number} the pixel format of the texture. defaults to gl.RGBA + * @param type {number} the gl type of the texture. defaults to gl.UNSIGNED_BYTE + */ +var Texture = function(gl, width, height, format, type) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + + /** + * The WebGL texture + * + * @member {WebGLTexture} + */ + this.texture = gl.createTexture(); + + /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() + * + * @member {Boolean} + */ + // some settings.. + this.mipmap = false; + + + /** + * Set to true to enable pre-multiplied alpha + * + * @member {Boolean} + */ + this.premultiplyAlpha = false; + + /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || gl.RGBA; + + /** + * The gl type of the texture. defaults to gl.UNSIGNED_BYTE + * + * @member {Number} + */ + this.type = type || gl.UNSIGNED_BYTE; + + +}; + +/** + * Uploads this texture to the GPU + * @param source {HTMLImageElement|ImageData|HTMLVideoElement} the source image of the texture + */ +Texture.prototype.upload = function(source) +{ + this.bind(); + + var gl = this.gl; + + + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + var newWidth = source.videoWidth || source.width; + var newHeight = source.videoHeight || source.height; + + if(newHeight !== this.height || newWidth !== this.width) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.format, this.type, source); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.format, this.type, source); + } + + // if the source is a video, we need to use the videoWidth / videoHeight properties as width / height will be incorrect. + this.width = newWidth; + this.height = newHeight; + +}; + +var FLOATING_POINT_AVAILABLE = false; + +/** + * Use a data source and uploads this texture to the GPU + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.prototype.uploadData = function(data, width, height) +{ + this.bind(); + + var gl = this.gl; + + + if(data instanceof Float32Array) + { + if(!FLOATING_POINT_AVAILABLE) + { + var ext = gl.getExtension("OES_texture_float"); + + if(ext) + { + FLOATING_POINT_AVAILABLE = true; + } + else + { + throw new Error('floating point textures not available'); + } + } + + this.type = gl.FLOAT; + } + else + { + // TODO support for other types + this.type = this.type || gl.UNSIGNED_BYTE; + } + + // what type of data? + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + + if(width !== this.width || height !== this.height) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, this.type, data || null); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, this.format, this.type, data || null); + } + + this.width = width; + this.height = height; + + +// texSubImage2D +}; + +/** + * Binds the texture + * @param location + */ +Texture.prototype.bind = function(location) +{ + var gl = this.gl; + + if(location !== undefined) + { + gl.activeTexture(gl.TEXTURE0 + location); + } + + gl.bindTexture(gl.TEXTURE_2D, this.texture); +}; + +/** + * Unbinds the texture + */ +Texture.prototype.unbind = function() +{ + var gl = this.gl; + gl.bindTexture(gl.TEXTURE_2D, null); +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.minFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + if(this.mipmap) + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR : gl.NEAREST); + } +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.magFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, linear ? gl.LINEAR : gl.NEAREST); +}; + +/** + * Enables mipmapping + */ +Texture.prototype.enableMipmap = function() +{ + var gl = this.gl; + + this.bind(); + + this.mipmap = true; + + gl.generateMipmap(gl.TEXTURE_2D); +}; + +/** + * Enables linear filtering + */ +Texture.prototype.enableLinearScaling = function() +{ + this.minFilter(true); + this.magFilter(true); +}; + +/** + * Enables nearest neighbour interpolation + */ +Texture.prototype.enableNearestScaling = function() +{ + this.minFilter(false); + this.magFilter(false); +}; + +/** + * Enables clamping on the texture so WebGL will not repeat it + */ +Texture.prototype.enableWrapClamp = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); +}; + +/** + * Enable tiling on the texture + */ +Texture.prototype.enableWrapRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); +}; + +Texture.prototype.enableWrapMirrorRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT); +}; + + +/** + * Destroys this texture + */ +Texture.prototype.destroy = function() +{ + var gl = this.gl; + //TODO + gl.deleteTexture(this.texture); +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param source {HTMLImageElement|ImageData} the source image of the texture + * @param premultiplyAlpha {Boolean} If we want to use pre-multiplied alpha + */ +Texture.fromSource = function(gl, source, premultiplyAlpha) +{ + var texture = new Texture(gl); + texture.premultiplyAlpha = premultiplyAlpha || false; + texture.upload(source); + + return texture; +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.fromData = function(gl, data, width, height) +{ + //console.log(data, width, height); + var texture = new Texture(gl); + texture.uploadData(data, width, height); + + return texture; +}; + + +module.exports = Texture; diff --git a/src/core/renderers/webgl/systems/textures/TextureGCSystem.js b/src/core/renderers/webgl/systems/textures/TextureGCSystem.js new file mode 100644 index 0000000..aff6b01 --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/TextureGCSystem.js @@ -0,0 +1,111 @@ +import WebGLSystem from '../WebGLSystem'; +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 TextureGCSystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; + 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.frameBuffer && 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.textureSystem; + + // 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/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js new file mode 100644 index 0000000..313f078 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js @@ -0,0 +1,41 @@ + +var mapType = require('./mapType'); +var mapSize = require('./mapSize'); + +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the attributes from + * @return attributes {Object} + */ +var extractAttributes = function(gl, program) +{ + var attributes = {}; + + var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + + for (var i = 0; i < totalAttributes; i++) + { + var attribData = gl.getActiveAttrib(program, i); + var type = mapType(gl, attribData.type); + + attributes[attribData.name] = { + type:type, + size:mapSize(type), + location:gl.getAttribLocation(program, attribData.name), + //TODO - make an attribute object + pointer: pointer + }; + } + + return attributes; +}; + +var pointer = function(type, normalized, stride, start){ + // console.log(this.location) + gl.vertexAttribPointer(this.location,this.size, type || gl.FLOAT, normalized || false, stride || 0, start || 0); +}; + +module.exports = extractAttributes; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js new file mode 100644 index 0000000..78d9f0d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js @@ -0,0 +1,35 @@ +var mapType = require('./mapType'); +var defaultValue = require('./defaultValue'); + +/** + * Extracts the uniforms + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the uniforms from + * @return uniforms {Object} + */ +var extractUniforms = function(gl, program) +{ + var uniforms = {}; + + var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + + for (var i = 0; i < totalUniforms; i++) + { + var uniformData = gl.getActiveUniform(program, i); + var name = uniformData.name.replace(/\[.*?\]/, ""); + var type = mapType(gl, uniformData.type ); + + uniforms[name] = { + type:type, + size:uniformData.size, + location:gl.getUniformLocation(program, name), + value:defaultValue(type, uniformData.size) + }; + } + + return uniforms; +}; + +module.exports = extractUniforms; diff --git a/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js new file mode 100644 index 0000000..c2aa597 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js @@ -0,0 +1,144 @@ +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param uniforms {Array} @mat ? + * @return attributes {Object} + */ +var generateUniformAccessObject = function(gl, uniformData) +{ + // this is the object we will be sending back. + // an object hierachy will be created for structs + var uniforms = {data:{}}; + + uniforms.gl = gl; + + var uniformKeys= Object.keys(uniformData); + + for (var i = 0; i < uniformKeys.length; i++) + { + var fullName = uniformKeys[i]; + + var nameTokens = fullName.split('.'); + var name = nameTokens[nameTokens.length - 1]; + + + var uniformGroup = getUniformGroup(nameTokens, uniforms); + + var uniform = uniformData[fullName]; + uniformGroup.data[name] = uniform; + + uniformGroup.gl = gl; + + Object.defineProperty(uniformGroup, name, { + get: generateGetter(name), + set: generateSetter(name, uniform) + }); + } + + return uniforms; +}; + +var generateGetter = function(name) +{ + var template = getterTemplate.replace('%%', name); + return new Function(template); // jshint ignore:line +}; + +var generateSetter = function(name, uniform) +{ + var template = setterTemplate.replace(/%%/g, name); + var setTemplate; + + if(uniform.size === 1) + { + setTemplate = GLSL_TO_SINGLE_SETTERS[uniform.type]; + } + else + { + setTemplate = GLSL_TO_ARRAY_SETTERS[uniform.type]; + } + + if(setTemplate) + { + template += "\nthis.gl." + setTemplate + ";"; + } + + return new Function('value', template); // jshint ignore:line +}; + +var getUniformGroup = function(nameTokens, uniform) +{ + var cur = uniform; + + for (var i = 0; i < nameTokens.length - 1; i++) + { + var o = cur[nameTokens[i]] || {data:{}}; + cur[nameTokens[i]] = o; + cur = o; + } + + return cur; +}; + +var getterTemplate = [ + 'return this.data.%%.value;', +].join('\n'); + +var setterTemplate = [ + 'this.data.%%.value = value;', + 'var location = this.data.%%.location;' +].join('\n'); + + +var GLSL_TO_SINGLE_SETTERS = { + + 'float': 'uniform1f(location, value)', + + 'vec2': 'uniform2f(location, value[0], value[1])', + 'vec3': 'uniform3f(location, value[0], value[1], value[2])', + 'vec4': 'uniform4f(location, value[0], value[1], value[2], value[3])', + + 'int': 'uniform1i(location, value)', + 'ivec2': 'uniform2i(location, value[0], value[1])', + 'ivec3': 'uniform3i(location, value[0], value[1], value[2])', + 'ivec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'bool': 'uniform1i(location, value)', + 'bvec2': 'uniform2i(location, value[0], value[1])', + 'bvec3': 'uniform3i(location, value[0], value[1], value[2])', + 'bvec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'mat2': 'uniformMatrix2fv(location, false, value)', + 'mat3': 'uniformMatrix3fv(location, false, value)', + 'mat4': 'uniformMatrix4fv(location, false, value)', + + 'sampler2D':'uniform1i(location, value)', + 'samplerCube': 'uniform1i(location, value)' + +}; + +var GLSL_TO_ARRAY_SETTERS = { + + 'float': 'uniform1fv(location, value)', + + 'vec2': 'uniform2fv(location, value)', + 'vec3': 'uniform3fv(location, value)', + 'vec4': 'uniform4fv(location, value)', + + 'int': 'uniform1iv(location, value)', + 'ivec2': 'uniform2iv(location, value)', + 'ivec3': 'uniform3iv(location, value)', + 'ivec4': 'uniform4iv(location, value)', + + 'bool': 'uniform1iv(location, value)', + 'bvec2': 'uniform2iv(location, value)', + 'bvec3': 'uniform3iv(location, value)', + 'bvec4': 'uniform4iv(location, value)', + + 'sampler2D':'uniform1iv(location, value)', + 'samplerCube': 'uniform1i(location, value)' +}; + +module.exports = generateUniformAccessObject; diff --git a/src/core/renderers/webgl/systems/shader/shader/index.js b/src/core/renderers/webgl/systems/shader/shader/index.js new file mode 100644 index 0000000..e08684d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/index.js @@ -0,0 +1,10 @@ +module.exports = { + compileProgram: require('./compileProgram'), + defaultValue: require('./defaultValue'), + extractAttributes: require('./extractAttributes'), + extractUniforms: require('./extractUniforms'), + generateUniformAccessObject: require('./generateUniformAccessObject'), + setPrecision: require('./setPrecision'), + mapSize: require('./mapSize'), + mapType: require('./mapType') +}; \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/shader/shader/mapSize.js b/src/core/renderers/webgl/systems/shader/shader/mapSize.js new file mode 100644 index 0000000..61d9b36 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapSize.js @@ -0,0 +1,36 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +var mapSize = function(type) +{ + return GLSL_TO_SIZE[type]; +}; + + +var GLSL_TO_SIZE = { + 'float': 1, + 'vec2': 2, + 'vec3': 3, + 'vec4': 4, + + 'int': 1, + 'ivec2': 2, + 'ivec3': 3, + 'ivec4': 4, + + 'bool': 1, + 'bvec2': 2, + 'bvec3': 3, + 'bvec4': 4, + + 'mat2': 4, + 'mat3': 9, + 'mat4': 16, + + 'sampler2D': 1 +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js new file mode 100644 index 0000000..581dace --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -0,0 +1,47 @@ + + +var mapSize = function(gl, type) +{ + if(!GL_TABLE) + { + var typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for(var i = 0; i < typeNames.length; ++i) + { + var tn = typeNames[i]; + GL_TABLE[ gl[tn] ] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +}; + +var GL_TABLE = null; + +var GL_TO_GLSL_TYPES = { + 'FLOAT': 'float', + 'FLOAT_VEC2': 'vec2', + 'FLOAT_VEC3': 'vec3', + 'FLOAT_VEC4': 'vec4', + + 'INT': 'int', + 'INT_VEC2': 'ivec2', + 'INT_VEC3': 'ivec3', + 'INT_VEC4': 'ivec4', + + 'BOOL': 'bool', + 'BOOL_VEC2': 'bvec2', + 'BOOL_VEC3': 'bvec3', + 'BOOL_VEC4': 'bvec4', + + 'FLOAT_MAT2': 'mat2', + 'FLOAT_MAT3': 'mat3', + 'FLOAT_MAT4': 'mat4', + + 'SAMPLER_2D': 'sampler2D', + 'SAMPLER_CUBE': 'samplerCube' +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/setPrecision.js b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js new file mode 100644 index 0000000..383459a --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +var setPrecision = function(src, precision) +{ + if(src.substring(0, 9) !== 'precision') + { + return 'precision ' + precision + ' float;\n' + src; + } + + return src; +}; + +module.exports = setPrecision; diff --git a/src/core/renderers/webgl/systems/textures/GLTexture.js b/src/core/renderers/webgl/systems/textures/GLTexture.js new file mode 100644 index 0000000..cb8f6ad --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/GLTexture.js @@ -0,0 +1,333 @@ + +/** + * Helper class to create a WebGL Texture + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL context + * @param width {number} the width of the texture + * @param height {number} the height of the texture + * @param format {number} the pixel format of the texture. defaults to gl.RGBA + * @param type {number} the gl type of the texture. defaults to gl.UNSIGNED_BYTE + */ +var Texture = function(gl, width, height, format, type) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + + /** + * The WebGL texture + * + * @member {WebGLTexture} + */ + this.texture = gl.createTexture(); + + /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() + * + * @member {Boolean} + */ + // some settings.. + this.mipmap = false; + + + /** + * Set to true to enable pre-multiplied alpha + * + * @member {Boolean} + */ + this.premultiplyAlpha = false; + + /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || gl.RGBA; + + /** + * The gl type of the texture. defaults to gl.UNSIGNED_BYTE + * + * @member {Number} + */ + this.type = type || gl.UNSIGNED_BYTE; + + +}; + +/** + * Uploads this texture to the GPU + * @param source {HTMLImageElement|ImageData|HTMLVideoElement} the source image of the texture + */ +Texture.prototype.upload = function(source) +{ + this.bind(); + + var gl = this.gl; + + + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + var newWidth = source.videoWidth || source.width; + var newHeight = source.videoHeight || source.height; + + if(newHeight !== this.height || newWidth !== this.width) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.format, this.type, source); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.format, this.type, source); + } + + // if the source is a video, we need to use the videoWidth / videoHeight properties as width / height will be incorrect. + this.width = newWidth; + this.height = newHeight; + +}; + +var FLOATING_POINT_AVAILABLE = false; + +/** + * Use a data source and uploads this texture to the GPU + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.prototype.uploadData = function(data, width, height) +{ + this.bind(); + + var gl = this.gl; + + + if(data instanceof Float32Array) + { + if(!FLOATING_POINT_AVAILABLE) + { + var ext = gl.getExtension("OES_texture_float"); + + if(ext) + { + FLOATING_POINT_AVAILABLE = true; + } + else + { + throw new Error('floating point textures not available'); + } + } + + this.type = gl.FLOAT; + } + else + { + // TODO support for other types + this.type = this.type || gl.UNSIGNED_BYTE; + } + + // what type of data? + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + + if(width !== this.width || height !== this.height) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, this.type, data || null); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, this.format, this.type, data || null); + } + + this.width = width; + this.height = height; + + +// texSubImage2D +}; + +/** + * Binds the texture + * @param location + */ +Texture.prototype.bind = function(location) +{ + var gl = this.gl; + + if(location !== undefined) + { + gl.activeTexture(gl.TEXTURE0 + location); + } + + gl.bindTexture(gl.TEXTURE_2D, this.texture); +}; + +/** + * Unbinds the texture + */ +Texture.prototype.unbind = function() +{ + var gl = this.gl; + gl.bindTexture(gl.TEXTURE_2D, null); +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.minFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + if(this.mipmap) + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR : gl.NEAREST); + } +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.magFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, linear ? gl.LINEAR : gl.NEAREST); +}; + +/** + * Enables mipmapping + */ +Texture.prototype.enableMipmap = function() +{ + var gl = this.gl; + + this.bind(); + + this.mipmap = true; + + gl.generateMipmap(gl.TEXTURE_2D); +}; + +/** + * Enables linear filtering + */ +Texture.prototype.enableLinearScaling = function() +{ + this.minFilter(true); + this.magFilter(true); +}; + +/** + * Enables nearest neighbour interpolation + */ +Texture.prototype.enableNearestScaling = function() +{ + this.minFilter(false); + this.magFilter(false); +}; + +/** + * Enables clamping on the texture so WebGL will not repeat it + */ +Texture.prototype.enableWrapClamp = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); +}; + +/** + * Enable tiling on the texture + */ +Texture.prototype.enableWrapRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); +}; + +Texture.prototype.enableWrapMirrorRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT); +}; + + +/** + * Destroys this texture + */ +Texture.prototype.destroy = function() +{ + var gl = this.gl; + //TODO + gl.deleteTexture(this.texture); +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param source {HTMLImageElement|ImageData} the source image of the texture + * @param premultiplyAlpha {Boolean} If we want to use pre-multiplied alpha + */ +Texture.fromSource = function(gl, source, premultiplyAlpha) +{ + var texture = new Texture(gl); + texture.premultiplyAlpha = premultiplyAlpha || false; + texture.upload(source); + + return texture; +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.fromData = function(gl, data, width, height) +{ + //console.log(data, width, height); + var texture = new Texture(gl); + texture.uploadData(data, width, height); + + return texture; +}; + + +module.exports = Texture; diff --git a/src/core/renderers/webgl/systems/textures/TextureGCSystem.js b/src/core/renderers/webgl/systems/textures/TextureGCSystem.js new file mode 100644 index 0000000..aff6b01 --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/TextureGCSystem.js @@ -0,0 +1,111 @@ +import WebGLSystem from '../WebGLSystem'; +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 TextureGCSystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; + 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.frameBuffer && 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.textureSystem; + + // 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/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js new file mode 100644 index 0000000..8416cec --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -0,0 +1,283 @@ +import WebGLSystem from '../WebGLSystem'; +import GLTexture from './GLTexture'; +import { removeItems } from '../../../../utils'; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ +export default class TextureSystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO set to max textures... + this.boundTextures = [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ]; + + this.currentLocation = -1; + + this.managedTextures = []; + + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // TODO move this.. to a nice make empty textures class.. + this.emptyTextures = {} + + this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); + this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); + + gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); + + for (var i = 0; i < 6; i++) + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + + 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) + { + + const gl = this.gl; + + + location = location || 0; + + if(this.currentLocation !== location) + { + this.currentLocation = location; + gl.activeTexture(gl.TEXTURE0 + location); + } + + if(texture) + { + texture = texture.baseTexture || texture; + + if(texture.valid) + { + + 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); + this.boundTextures[location] = null; + } + } + + unbind(texture) + { + const gl = this.gl; + + for (var i = 0; i < this.boundTextures.length; i++) { + + if(this.boundTextures[i] === texture) + { + if(this.currentLocation !== i) + { + gl.activeTexture(gl.TEXTURE0 + i); + this.currentLocation = i; + } + + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); + this.boundTextures[i] = null; + } + } + } + + initTexture(texture) + { + const gl = this.gl; + + var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); + glTexture.premultiplyAlpha = texture.premultiplyAlpha; + // guarentee an update.. + glTexture.dirtyId = -1; + + texture._glTextures[this.CONTEXT_UID] = glTexture; + + this.managedTextures.push(texture); + texture.on('dispose', this.destroyTexture, this); + + return glTexture; + } + + updateTexture(texture) + { + const glTexture = texture._glTextures[this.CONTEXT_UID]; + const gl = this.gl; + + // TODO there are only 3 textures as far as im aware? + // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) + if(texture.target === gl.TEXTURE_CUBE_MAP) + { + // console.log( gl.UNSIGNED_BYTE) + for (var i = 0; i < texture.sides.length; i++) + { + // TODO - we should only upload what changed.. + // but im sure this is not going to be a problem just yet! + var texturePart = texture.sides[i]; + + if(texturePart.resource) + { + if(texturePart.resource.uploadable) + { + + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.format, + texture.type, + texturePart.resource.source); + } + else + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.width, + texture.height, + 0, + texture.format, + texture.type, + texturePart.resource.source); + } + } + else + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.width, + texture.height, + 0, + texture.format, + texture.type, + null); + } + } + } + else + { + if(texture.resource) + { + if(texture.resource.uploadable) + { + glTexture.upload(texture.resource.source); + + } + else + { + glTexture.uploadData(texture.resource.source, texture.width, texture.height); + } + } + else + { + glTexture.uploadData(null, texture.width, texture.height); + } + } + + // lets only update what changes.. + this.setStyle(texture); + } + + /** + * Deletes the texture from WebGL + * + * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy + * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) + { + texture = texture.baseTexture || texture; + + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + this.unbind(texture); + + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + const i = this.managedTextures.indexOf(texture); + + if (i !== -1) + { + removeItems(this.managedTextures, i, 1); + } + } + } + } + + setStyle(texture) + { + const gl = this.gl; + + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); + + if(texture.mipmap) + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } + + gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } +} \ No newline at end of file diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js new file mode 100644 index 0000000..313f078 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js @@ -0,0 +1,41 @@ + +var mapType = require('./mapType'); +var mapSize = require('./mapSize'); + +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the attributes from + * @return attributes {Object} + */ +var extractAttributes = function(gl, program) +{ + var attributes = {}; + + var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + + for (var i = 0; i < totalAttributes; i++) + { + var attribData = gl.getActiveAttrib(program, i); + var type = mapType(gl, attribData.type); + + attributes[attribData.name] = { + type:type, + size:mapSize(type), + location:gl.getAttribLocation(program, attribData.name), + //TODO - make an attribute object + pointer: pointer + }; + } + + return attributes; +}; + +var pointer = function(type, normalized, stride, start){ + // console.log(this.location) + gl.vertexAttribPointer(this.location,this.size, type || gl.FLOAT, normalized || false, stride || 0, start || 0); +}; + +module.exports = extractAttributes; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js new file mode 100644 index 0000000..78d9f0d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js @@ -0,0 +1,35 @@ +var mapType = require('./mapType'); +var defaultValue = require('./defaultValue'); + +/** + * Extracts the uniforms + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the uniforms from + * @return uniforms {Object} + */ +var extractUniforms = function(gl, program) +{ + var uniforms = {}; + + var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + + for (var i = 0; i < totalUniforms; i++) + { + var uniformData = gl.getActiveUniform(program, i); + var name = uniformData.name.replace(/\[.*?\]/, ""); + var type = mapType(gl, uniformData.type ); + + uniforms[name] = { + type:type, + size:uniformData.size, + location:gl.getUniformLocation(program, name), + value:defaultValue(type, uniformData.size) + }; + } + + return uniforms; +}; + +module.exports = extractUniforms; diff --git a/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js new file mode 100644 index 0000000..c2aa597 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js @@ -0,0 +1,144 @@ +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param uniforms {Array} @mat ? + * @return attributes {Object} + */ +var generateUniformAccessObject = function(gl, uniformData) +{ + // this is the object we will be sending back. + // an object hierachy will be created for structs + var uniforms = {data:{}}; + + uniforms.gl = gl; + + var uniformKeys= Object.keys(uniformData); + + for (var i = 0; i < uniformKeys.length; i++) + { + var fullName = uniformKeys[i]; + + var nameTokens = fullName.split('.'); + var name = nameTokens[nameTokens.length - 1]; + + + var uniformGroup = getUniformGroup(nameTokens, uniforms); + + var uniform = uniformData[fullName]; + uniformGroup.data[name] = uniform; + + uniformGroup.gl = gl; + + Object.defineProperty(uniformGroup, name, { + get: generateGetter(name), + set: generateSetter(name, uniform) + }); + } + + return uniforms; +}; + +var generateGetter = function(name) +{ + var template = getterTemplate.replace('%%', name); + return new Function(template); // jshint ignore:line +}; + +var generateSetter = function(name, uniform) +{ + var template = setterTemplate.replace(/%%/g, name); + var setTemplate; + + if(uniform.size === 1) + { + setTemplate = GLSL_TO_SINGLE_SETTERS[uniform.type]; + } + else + { + setTemplate = GLSL_TO_ARRAY_SETTERS[uniform.type]; + } + + if(setTemplate) + { + template += "\nthis.gl." + setTemplate + ";"; + } + + return new Function('value', template); // jshint ignore:line +}; + +var getUniformGroup = function(nameTokens, uniform) +{ + var cur = uniform; + + for (var i = 0; i < nameTokens.length - 1; i++) + { + var o = cur[nameTokens[i]] || {data:{}}; + cur[nameTokens[i]] = o; + cur = o; + } + + return cur; +}; + +var getterTemplate = [ + 'return this.data.%%.value;', +].join('\n'); + +var setterTemplate = [ + 'this.data.%%.value = value;', + 'var location = this.data.%%.location;' +].join('\n'); + + +var GLSL_TO_SINGLE_SETTERS = { + + 'float': 'uniform1f(location, value)', + + 'vec2': 'uniform2f(location, value[0], value[1])', + 'vec3': 'uniform3f(location, value[0], value[1], value[2])', + 'vec4': 'uniform4f(location, value[0], value[1], value[2], value[3])', + + 'int': 'uniform1i(location, value)', + 'ivec2': 'uniform2i(location, value[0], value[1])', + 'ivec3': 'uniform3i(location, value[0], value[1], value[2])', + 'ivec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'bool': 'uniform1i(location, value)', + 'bvec2': 'uniform2i(location, value[0], value[1])', + 'bvec3': 'uniform3i(location, value[0], value[1], value[2])', + 'bvec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'mat2': 'uniformMatrix2fv(location, false, value)', + 'mat3': 'uniformMatrix3fv(location, false, value)', + 'mat4': 'uniformMatrix4fv(location, false, value)', + + 'sampler2D':'uniform1i(location, value)', + 'samplerCube': 'uniform1i(location, value)' + +}; + +var GLSL_TO_ARRAY_SETTERS = { + + 'float': 'uniform1fv(location, value)', + + 'vec2': 'uniform2fv(location, value)', + 'vec3': 'uniform3fv(location, value)', + 'vec4': 'uniform4fv(location, value)', + + 'int': 'uniform1iv(location, value)', + 'ivec2': 'uniform2iv(location, value)', + 'ivec3': 'uniform3iv(location, value)', + 'ivec4': 'uniform4iv(location, value)', + + 'bool': 'uniform1iv(location, value)', + 'bvec2': 'uniform2iv(location, value)', + 'bvec3': 'uniform3iv(location, value)', + 'bvec4': 'uniform4iv(location, value)', + + 'sampler2D':'uniform1iv(location, value)', + 'samplerCube': 'uniform1i(location, value)' +}; + +module.exports = generateUniformAccessObject; diff --git a/src/core/renderers/webgl/systems/shader/shader/index.js b/src/core/renderers/webgl/systems/shader/shader/index.js new file mode 100644 index 0000000..e08684d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/index.js @@ -0,0 +1,10 @@ +module.exports = { + compileProgram: require('./compileProgram'), + defaultValue: require('./defaultValue'), + extractAttributes: require('./extractAttributes'), + extractUniforms: require('./extractUniforms'), + generateUniformAccessObject: require('./generateUniformAccessObject'), + setPrecision: require('./setPrecision'), + mapSize: require('./mapSize'), + mapType: require('./mapType') +}; \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/shader/shader/mapSize.js b/src/core/renderers/webgl/systems/shader/shader/mapSize.js new file mode 100644 index 0000000..61d9b36 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapSize.js @@ -0,0 +1,36 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +var mapSize = function(type) +{ + return GLSL_TO_SIZE[type]; +}; + + +var GLSL_TO_SIZE = { + 'float': 1, + 'vec2': 2, + 'vec3': 3, + 'vec4': 4, + + 'int': 1, + 'ivec2': 2, + 'ivec3': 3, + 'ivec4': 4, + + 'bool': 1, + 'bvec2': 2, + 'bvec3': 3, + 'bvec4': 4, + + 'mat2': 4, + 'mat3': 9, + 'mat4': 16, + + 'sampler2D': 1 +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js new file mode 100644 index 0000000..581dace --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -0,0 +1,47 @@ + + +var mapSize = function(gl, type) +{ + if(!GL_TABLE) + { + var typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for(var i = 0; i < typeNames.length; ++i) + { + var tn = typeNames[i]; + GL_TABLE[ gl[tn] ] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +}; + +var GL_TABLE = null; + +var GL_TO_GLSL_TYPES = { + 'FLOAT': 'float', + 'FLOAT_VEC2': 'vec2', + 'FLOAT_VEC3': 'vec3', + 'FLOAT_VEC4': 'vec4', + + 'INT': 'int', + 'INT_VEC2': 'ivec2', + 'INT_VEC3': 'ivec3', + 'INT_VEC4': 'ivec4', + + 'BOOL': 'bool', + 'BOOL_VEC2': 'bvec2', + 'BOOL_VEC3': 'bvec3', + 'BOOL_VEC4': 'bvec4', + + 'FLOAT_MAT2': 'mat2', + 'FLOAT_MAT3': 'mat3', + 'FLOAT_MAT4': 'mat4', + + 'SAMPLER_2D': 'sampler2D', + 'SAMPLER_CUBE': 'samplerCube' +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/setPrecision.js b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js new file mode 100644 index 0000000..383459a --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +var setPrecision = function(src, precision) +{ + if(src.substring(0, 9) !== 'precision') + { + return 'precision ' + precision + ' float;\n' + src; + } + + return src; +}; + +module.exports = setPrecision; diff --git a/src/core/renderers/webgl/systems/textures/GLTexture.js b/src/core/renderers/webgl/systems/textures/GLTexture.js new file mode 100644 index 0000000..cb8f6ad --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/GLTexture.js @@ -0,0 +1,333 @@ + +/** + * Helper class to create a WebGL Texture + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL context + * @param width {number} the width of the texture + * @param height {number} the height of the texture + * @param format {number} the pixel format of the texture. defaults to gl.RGBA + * @param type {number} the gl type of the texture. defaults to gl.UNSIGNED_BYTE + */ +var Texture = function(gl, width, height, format, type) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + + /** + * The WebGL texture + * + * @member {WebGLTexture} + */ + this.texture = gl.createTexture(); + + /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() + * + * @member {Boolean} + */ + // some settings.. + this.mipmap = false; + + + /** + * Set to true to enable pre-multiplied alpha + * + * @member {Boolean} + */ + this.premultiplyAlpha = false; + + /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || gl.RGBA; + + /** + * The gl type of the texture. defaults to gl.UNSIGNED_BYTE + * + * @member {Number} + */ + this.type = type || gl.UNSIGNED_BYTE; + + +}; + +/** + * Uploads this texture to the GPU + * @param source {HTMLImageElement|ImageData|HTMLVideoElement} the source image of the texture + */ +Texture.prototype.upload = function(source) +{ + this.bind(); + + var gl = this.gl; + + + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + var newWidth = source.videoWidth || source.width; + var newHeight = source.videoHeight || source.height; + + if(newHeight !== this.height || newWidth !== this.width) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.format, this.type, source); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.format, this.type, source); + } + + // if the source is a video, we need to use the videoWidth / videoHeight properties as width / height will be incorrect. + this.width = newWidth; + this.height = newHeight; + +}; + +var FLOATING_POINT_AVAILABLE = false; + +/** + * Use a data source and uploads this texture to the GPU + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.prototype.uploadData = function(data, width, height) +{ + this.bind(); + + var gl = this.gl; + + + if(data instanceof Float32Array) + { + if(!FLOATING_POINT_AVAILABLE) + { + var ext = gl.getExtension("OES_texture_float"); + + if(ext) + { + FLOATING_POINT_AVAILABLE = true; + } + else + { + throw new Error('floating point textures not available'); + } + } + + this.type = gl.FLOAT; + } + else + { + // TODO support for other types + this.type = this.type || gl.UNSIGNED_BYTE; + } + + // what type of data? + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + + if(width !== this.width || height !== this.height) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, this.type, data || null); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, this.format, this.type, data || null); + } + + this.width = width; + this.height = height; + + +// texSubImage2D +}; + +/** + * Binds the texture + * @param location + */ +Texture.prototype.bind = function(location) +{ + var gl = this.gl; + + if(location !== undefined) + { + gl.activeTexture(gl.TEXTURE0 + location); + } + + gl.bindTexture(gl.TEXTURE_2D, this.texture); +}; + +/** + * Unbinds the texture + */ +Texture.prototype.unbind = function() +{ + var gl = this.gl; + gl.bindTexture(gl.TEXTURE_2D, null); +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.minFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + if(this.mipmap) + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR : gl.NEAREST); + } +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.magFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, linear ? gl.LINEAR : gl.NEAREST); +}; + +/** + * Enables mipmapping + */ +Texture.prototype.enableMipmap = function() +{ + var gl = this.gl; + + this.bind(); + + this.mipmap = true; + + gl.generateMipmap(gl.TEXTURE_2D); +}; + +/** + * Enables linear filtering + */ +Texture.prototype.enableLinearScaling = function() +{ + this.minFilter(true); + this.magFilter(true); +}; + +/** + * Enables nearest neighbour interpolation + */ +Texture.prototype.enableNearestScaling = function() +{ + this.minFilter(false); + this.magFilter(false); +}; + +/** + * Enables clamping on the texture so WebGL will not repeat it + */ +Texture.prototype.enableWrapClamp = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); +}; + +/** + * Enable tiling on the texture + */ +Texture.prototype.enableWrapRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); +}; + +Texture.prototype.enableWrapMirrorRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT); +}; + + +/** + * Destroys this texture + */ +Texture.prototype.destroy = function() +{ + var gl = this.gl; + //TODO + gl.deleteTexture(this.texture); +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param source {HTMLImageElement|ImageData} the source image of the texture + * @param premultiplyAlpha {Boolean} If we want to use pre-multiplied alpha + */ +Texture.fromSource = function(gl, source, premultiplyAlpha) +{ + var texture = new Texture(gl); + texture.premultiplyAlpha = premultiplyAlpha || false; + texture.upload(source); + + return texture; +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.fromData = function(gl, data, width, height) +{ + //console.log(data, width, height); + var texture = new Texture(gl); + texture.uploadData(data, width, height); + + return texture; +}; + + +module.exports = Texture; diff --git a/src/core/renderers/webgl/systems/textures/TextureGCSystem.js b/src/core/renderers/webgl/systems/textures/TextureGCSystem.js new file mode 100644 index 0000000..aff6b01 --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/TextureGCSystem.js @@ -0,0 +1,111 @@ +import WebGLSystem from '../WebGLSystem'; +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 TextureGCSystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; + 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.frameBuffer && 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.textureSystem; + + // 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/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js new file mode 100644 index 0000000..8416cec --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -0,0 +1,283 @@ +import WebGLSystem from '../WebGLSystem'; +import GLTexture from './GLTexture'; +import { removeItems } from '../../../../utils'; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ +export default class TextureSystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO set to max textures... + this.boundTextures = [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ]; + + this.currentLocation = -1; + + this.managedTextures = []; + + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // TODO move this.. to a nice make empty textures class.. + this.emptyTextures = {} + + this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); + this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); + + gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); + + for (var i = 0; i < 6; i++) + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + + 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) + { + + const gl = this.gl; + + + location = location || 0; + + if(this.currentLocation !== location) + { + this.currentLocation = location; + gl.activeTexture(gl.TEXTURE0 + location); + } + + if(texture) + { + texture = texture.baseTexture || texture; + + if(texture.valid) + { + + 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); + this.boundTextures[location] = null; + } + } + + unbind(texture) + { + const gl = this.gl; + + for (var i = 0; i < this.boundTextures.length; i++) { + + if(this.boundTextures[i] === texture) + { + if(this.currentLocation !== i) + { + gl.activeTexture(gl.TEXTURE0 + i); + this.currentLocation = i; + } + + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); + this.boundTextures[i] = null; + } + } + } + + initTexture(texture) + { + const gl = this.gl; + + var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); + glTexture.premultiplyAlpha = texture.premultiplyAlpha; + // guarentee an update.. + glTexture.dirtyId = -1; + + texture._glTextures[this.CONTEXT_UID] = glTexture; + + this.managedTextures.push(texture); + texture.on('dispose', this.destroyTexture, this); + + return glTexture; + } + + updateTexture(texture) + { + const glTexture = texture._glTextures[this.CONTEXT_UID]; + const gl = this.gl; + + // TODO there are only 3 textures as far as im aware? + // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) + if(texture.target === gl.TEXTURE_CUBE_MAP) + { + // console.log( gl.UNSIGNED_BYTE) + for (var i = 0; i < texture.sides.length; i++) + { + // TODO - we should only upload what changed.. + // but im sure this is not going to be a problem just yet! + var texturePart = texture.sides[i]; + + if(texturePart.resource) + { + if(texturePart.resource.uploadable) + { + + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.format, + texture.type, + texturePart.resource.source); + } + else + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.width, + texture.height, + 0, + texture.format, + texture.type, + texturePart.resource.source); + } + } + else + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.width, + texture.height, + 0, + texture.format, + texture.type, + null); + } + } + } + else + { + if(texture.resource) + { + if(texture.resource.uploadable) + { + glTexture.upload(texture.resource.source); + + } + else + { + glTexture.uploadData(texture.resource.source, texture.width, texture.height); + } + } + else + { + glTexture.uploadData(null, texture.width, texture.height); + } + } + + // lets only update what changes.. + this.setStyle(texture); + } + + /** + * Deletes the texture from WebGL + * + * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy + * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) + { + texture = texture.baseTexture || texture; + + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + this.unbind(texture); + + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + const i = this.managedTextures.indexOf(texture); + + if (i !== -1) + { + removeItems(this.managedTextures, i, 1); + } + } + } + } + + setStyle(texture) + { + const gl = this.gl; + + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); + + if(texture.mipmap) + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } + + gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } +} \ No newline at end of file diff --git a/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js b/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js index 8326e63..3544cb9 100644 --- a/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js +++ b/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js @@ -11,8 +11,6 @@ export default function checkMaxIfStatmentsInShader(maxIfs, gl) { - const createTempContext = !gl; - // @if DEBUG if (maxIfs === 0) { @@ -20,16 +18,6 @@ } // @endif - if (createTempContext) - { - const tinyCanvas = document.createElement('canvas'); - - tinyCanvas.width = 1; - tinyCanvas.height = 1; - - gl = glCore.createContext(tinyCanvas); - } - const shader = gl.createShader(gl.FRAGMENT_SHADER); while (true) // eslint-disable-line no-constant-condition @@ -50,15 +38,6 @@ } } - if (createTempContext) - { - // get rid of context - if (gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').loseContext(); - } - } - return maxIfs; } diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js new file mode 100644 index 0000000..313f078 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js @@ -0,0 +1,41 @@ + +var mapType = require('./mapType'); +var mapSize = require('./mapSize'); + +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the attributes from + * @return attributes {Object} + */ +var extractAttributes = function(gl, program) +{ + var attributes = {}; + + var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + + for (var i = 0; i < totalAttributes; i++) + { + var attribData = gl.getActiveAttrib(program, i); + var type = mapType(gl, attribData.type); + + attributes[attribData.name] = { + type:type, + size:mapSize(type), + location:gl.getAttribLocation(program, attribData.name), + //TODO - make an attribute object + pointer: pointer + }; + } + + return attributes; +}; + +var pointer = function(type, normalized, stride, start){ + // console.log(this.location) + gl.vertexAttribPointer(this.location,this.size, type || gl.FLOAT, normalized || false, stride || 0, start || 0); +}; + +module.exports = extractAttributes; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js new file mode 100644 index 0000000..78d9f0d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js @@ -0,0 +1,35 @@ +var mapType = require('./mapType'); +var defaultValue = require('./defaultValue'); + +/** + * Extracts the uniforms + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the uniforms from + * @return uniforms {Object} + */ +var extractUniforms = function(gl, program) +{ + var uniforms = {}; + + var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + + for (var i = 0; i < totalUniforms; i++) + { + var uniformData = gl.getActiveUniform(program, i); + var name = uniformData.name.replace(/\[.*?\]/, ""); + var type = mapType(gl, uniformData.type ); + + uniforms[name] = { + type:type, + size:uniformData.size, + location:gl.getUniformLocation(program, name), + value:defaultValue(type, uniformData.size) + }; + } + + return uniforms; +}; + +module.exports = extractUniforms; diff --git a/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js new file mode 100644 index 0000000..c2aa597 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js @@ -0,0 +1,144 @@ +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param uniforms {Array} @mat ? + * @return attributes {Object} + */ +var generateUniformAccessObject = function(gl, uniformData) +{ + // this is the object we will be sending back. + // an object hierachy will be created for structs + var uniforms = {data:{}}; + + uniforms.gl = gl; + + var uniformKeys= Object.keys(uniformData); + + for (var i = 0; i < uniformKeys.length; i++) + { + var fullName = uniformKeys[i]; + + var nameTokens = fullName.split('.'); + var name = nameTokens[nameTokens.length - 1]; + + + var uniformGroup = getUniformGroup(nameTokens, uniforms); + + var uniform = uniformData[fullName]; + uniformGroup.data[name] = uniform; + + uniformGroup.gl = gl; + + Object.defineProperty(uniformGroup, name, { + get: generateGetter(name), + set: generateSetter(name, uniform) + }); + } + + return uniforms; +}; + +var generateGetter = function(name) +{ + var template = getterTemplate.replace('%%', name); + return new Function(template); // jshint ignore:line +}; + +var generateSetter = function(name, uniform) +{ + var template = setterTemplate.replace(/%%/g, name); + var setTemplate; + + if(uniform.size === 1) + { + setTemplate = GLSL_TO_SINGLE_SETTERS[uniform.type]; + } + else + { + setTemplate = GLSL_TO_ARRAY_SETTERS[uniform.type]; + } + + if(setTemplate) + { + template += "\nthis.gl." + setTemplate + ";"; + } + + return new Function('value', template); // jshint ignore:line +}; + +var getUniformGroup = function(nameTokens, uniform) +{ + var cur = uniform; + + for (var i = 0; i < nameTokens.length - 1; i++) + { + var o = cur[nameTokens[i]] || {data:{}}; + cur[nameTokens[i]] = o; + cur = o; + } + + return cur; +}; + +var getterTemplate = [ + 'return this.data.%%.value;', +].join('\n'); + +var setterTemplate = [ + 'this.data.%%.value = value;', + 'var location = this.data.%%.location;' +].join('\n'); + + +var GLSL_TO_SINGLE_SETTERS = { + + 'float': 'uniform1f(location, value)', + + 'vec2': 'uniform2f(location, value[0], value[1])', + 'vec3': 'uniform3f(location, value[0], value[1], value[2])', + 'vec4': 'uniform4f(location, value[0], value[1], value[2], value[3])', + + 'int': 'uniform1i(location, value)', + 'ivec2': 'uniform2i(location, value[0], value[1])', + 'ivec3': 'uniform3i(location, value[0], value[1], value[2])', + 'ivec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'bool': 'uniform1i(location, value)', + 'bvec2': 'uniform2i(location, value[0], value[1])', + 'bvec3': 'uniform3i(location, value[0], value[1], value[2])', + 'bvec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'mat2': 'uniformMatrix2fv(location, false, value)', + 'mat3': 'uniformMatrix3fv(location, false, value)', + 'mat4': 'uniformMatrix4fv(location, false, value)', + + 'sampler2D':'uniform1i(location, value)', + 'samplerCube': 'uniform1i(location, value)' + +}; + +var GLSL_TO_ARRAY_SETTERS = { + + 'float': 'uniform1fv(location, value)', + + 'vec2': 'uniform2fv(location, value)', + 'vec3': 'uniform3fv(location, value)', + 'vec4': 'uniform4fv(location, value)', + + 'int': 'uniform1iv(location, value)', + 'ivec2': 'uniform2iv(location, value)', + 'ivec3': 'uniform3iv(location, value)', + 'ivec4': 'uniform4iv(location, value)', + + 'bool': 'uniform1iv(location, value)', + 'bvec2': 'uniform2iv(location, value)', + 'bvec3': 'uniform3iv(location, value)', + 'bvec4': 'uniform4iv(location, value)', + + 'sampler2D':'uniform1iv(location, value)', + 'samplerCube': 'uniform1i(location, value)' +}; + +module.exports = generateUniformAccessObject; diff --git a/src/core/renderers/webgl/systems/shader/shader/index.js b/src/core/renderers/webgl/systems/shader/shader/index.js new file mode 100644 index 0000000..e08684d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/index.js @@ -0,0 +1,10 @@ +module.exports = { + compileProgram: require('./compileProgram'), + defaultValue: require('./defaultValue'), + extractAttributes: require('./extractAttributes'), + extractUniforms: require('./extractUniforms'), + generateUniformAccessObject: require('./generateUniformAccessObject'), + setPrecision: require('./setPrecision'), + mapSize: require('./mapSize'), + mapType: require('./mapType') +}; \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/shader/shader/mapSize.js b/src/core/renderers/webgl/systems/shader/shader/mapSize.js new file mode 100644 index 0000000..61d9b36 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapSize.js @@ -0,0 +1,36 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +var mapSize = function(type) +{ + return GLSL_TO_SIZE[type]; +}; + + +var GLSL_TO_SIZE = { + 'float': 1, + 'vec2': 2, + 'vec3': 3, + 'vec4': 4, + + 'int': 1, + 'ivec2': 2, + 'ivec3': 3, + 'ivec4': 4, + + 'bool': 1, + 'bvec2': 2, + 'bvec3': 3, + 'bvec4': 4, + + 'mat2': 4, + 'mat3': 9, + 'mat4': 16, + + 'sampler2D': 1 +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js new file mode 100644 index 0000000..581dace --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -0,0 +1,47 @@ + + +var mapSize = function(gl, type) +{ + if(!GL_TABLE) + { + var typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for(var i = 0; i < typeNames.length; ++i) + { + var tn = typeNames[i]; + GL_TABLE[ gl[tn] ] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +}; + +var GL_TABLE = null; + +var GL_TO_GLSL_TYPES = { + 'FLOAT': 'float', + 'FLOAT_VEC2': 'vec2', + 'FLOAT_VEC3': 'vec3', + 'FLOAT_VEC4': 'vec4', + + 'INT': 'int', + 'INT_VEC2': 'ivec2', + 'INT_VEC3': 'ivec3', + 'INT_VEC4': 'ivec4', + + 'BOOL': 'bool', + 'BOOL_VEC2': 'bvec2', + 'BOOL_VEC3': 'bvec3', + 'BOOL_VEC4': 'bvec4', + + 'FLOAT_MAT2': 'mat2', + 'FLOAT_MAT3': 'mat3', + 'FLOAT_MAT4': 'mat4', + + 'SAMPLER_2D': 'sampler2D', + 'SAMPLER_CUBE': 'samplerCube' +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/setPrecision.js b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js new file mode 100644 index 0000000..383459a --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +var setPrecision = function(src, precision) +{ + if(src.substring(0, 9) !== 'precision') + { + return 'precision ' + precision + ' float;\n' + src; + } + + return src; +}; + +module.exports = setPrecision; diff --git a/src/core/renderers/webgl/systems/textures/GLTexture.js b/src/core/renderers/webgl/systems/textures/GLTexture.js new file mode 100644 index 0000000..cb8f6ad --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/GLTexture.js @@ -0,0 +1,333 @@ + +/** + * Helper class to create a WebGL Texture + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL context + * @param width {number} the width of the texture + * @param height {number} the height of the texture + * @param format {number} the pixel format of the texture. defaults to gl.RGBA + * @param type {number} the gl type of the texture. defaults to gl.UNSIGNED_BYTE + */ +var Texture = function(gl, width, height, format, type) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + + /** + * The WebGL texture + * + * @member {WebGLTexture} + */ + this.texture = gl.createTexture(); + + /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() + * + * @member {Boolean} + */ + // some settings.. + this.mipmap = false; + + + /** + * Set to true to enable pre-multiplied alpha + * + * @member {Boolean} + */ + this.premultiplyAlpha = false; + + /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || gl.RGBA; + + /** + * The gl type of the texture. defaults to gl.UNSIGNED_BYTE + * + * @member {Number} + */ + this.type = type || gl.UNSIGNED_BYTE; + + +}; + +/** + * Uploads this texture to the GPU + * @param source {HTMLImageElement|ImageData|HTMLVideoElement} the source image of the texture + */ +Texture.prototype.upload = function(source) +{ + this.bind(); + + var gl = this.gl; + + + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + var newWidth = source.videoWidth || source.width; + var newHeight = source.videoHeight || source.height; + + if(newHeight !== this.height || newWidth !== this.width) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.format, this.type, source); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.format, this.type, source); + } + + // if the source is a video, we need to use the videoWidth / videoHeight properties as width / height will be incorrect. + this.width = newWidth; + this.height = newHeight; + +}; + +var FLOATING_POINT_AVAILABLE = false; + +/** + * Use a data source and uploads this texture to the GPU + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.prototype.uploadData = function(data, width, height) +{ + this.bind(); + + var gl = this.gl; + + + if(data instanceof Float32Array) + { + if(!FLOATING_POINT_AVAILABLE) + { + var ext = gl.getExtension("OES_texture_float"); + + if(ext) + { + FLOATING_POINT_AVAILABLE = true; + } + else + { + throw new Error('floating point textures not available'); + } + } + + this.type = gl.FLOAT; + } + else + { + // TODO support for other types + this.type = this.type || gl.UNSIGNED_BYTE; + } + + // what type of data? + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + + if(width !== this.width || height !== this.height) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, this.type, data || null); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, this.format, this.type, data || null); + } + + this.width = width; + this.height = height; + + +// texSubImage2D +}; + +/** + * Binds the texture + * @param location + */ +Texture.prototype.bind = function(location) +{ + var gl = this.gl; + + if(location !== undefined) + { + gl.activeTexture(gl.TEXTURE0 + location); + } + + gl.bindTexture(gl.TEXTURE_2D, this.texture); +}; + +/** + * Unbinds the texture + */ +Texture.prototype.unbind = function() +{ + var gl = this.gl; + gl.bindTexture(gl.TEXTURE_2D, null); +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.minFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + if(this.mipmap) + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR : gl.NEAREST); + } +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.magFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, linear ? gl.LINEAR : gl.NEAREST); +}; + +/** + * Enables mipmapping + */ +Texture.prototype.enableMipmap = function() +{ + var gl = this.gl; + + this.bind(); + + this.mipmap = true; + + gl.generateMipmap(gl.TEXTURE_2D); +}; + +/** + * Enables linear filtering + */ +Texture.prototype.enableLinearScaling = function() +{ + this.minFilter(true); + this.magFilter(true); +}; + +/** + * Enables nearest neighbour interpolation + */ +Texture.prototype.enableNearestScaling = function() +{ + this.minFilter(false); + this.magFilter(false); +}; + +/** + * Enables clamping on the texture so WebGL will not repeat it + */ +Texture.prototype.enableWrapClamp = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); +}; + +/** + * Enable tiling on the texture + */ +Texture.prototype.enableWrapRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); +}; + +Texture.prototype.enableWrapMirrorRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT); +}; + + +/** + * Destroys this texture + */ +Texture.prototype.destroy = function() +{ + var gl = this.gl; + //TODO + gl.deleteTexture(this.texture); +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param source {HTMLImageElement|ImageData} the source image of the texture + * @param premultiplyAlpha {Boolean} If we want to use pre-multiplied alpha + */ +Texture.fromSource = function(gl, source, premultiplyAlpha) +{ + var texture = new Texture(gl); + texture.premultiplyAlpha = premultiplyAlpha || false; + texture.upload(source); + + return texture; +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.fromData = function(gl, data, width, height) +{ + //console.log(data, width, height); + var texture = new Texture(gl); + texture.uploadData(data, width, height); + + return texture; +}; + + +module.exports = Texture; diff --git a/src/core/renderers/webgl/systems/textures/TextureGCSystem.js b/src/core/renderers/webgl/systems/textures/TextureGCSystem.js new file mode 100644 index 0000000..aff6b01 --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/TextureGCSystem.js @@ -0,0 +1,111 @@ +import WebGLSystem from '../WebGLSystem'; +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 TextureGCSystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; + 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.frameBuffer && 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.textureSystem; + + // 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/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js new file mode 100644 index 0000000..8416cec --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -0,0 +1,283 @@ +import WebGLSystem from '../WebGLSystem'; +import GLTexture from './GLTexture'; +import { removeItems } from '../../../../utils'; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ +export default class TextureSystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO set to max textures... + this.boundTextures = [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ]; + + this.currentLocation = -1; + + this.managedTextures = []; + + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // TODO move this.. to a nice make empty textures class.. + this.emptyTextures = {} + + this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); + this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); + + gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); + + for (var i = 0; i < 6; i++) + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + + 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) + { + + const gl = this.gl; + + + location = location || 0; + + if(this.currentLocation !== location) + { + this.currentLocation = location; + gl.activeTexture(gl.TEXTURE0 + location); + } + + if(texture) + { + texture = texture.baseTexture || texture; + + if(texture.valid) + { + + 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); + this.boundTextures[location] = null; + } + } + + unbind(texture) + { + const gl = this.gl; + + for (var i = 0; i < this.boundTextures.length; i++) { + + if(this.boundTextures[i] === texture) + { + if(this.currentLocation !== i) + { + gl.activeTexture(gl.TEXTURE0 + i); + this.currentLocation = i; + } + + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); + this.boundTextures[i] = null; + } + } + } + + initTexture(texture) + { + const gl = this.gl; + + var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); + glTexture.premultiplyAlpha = texture.premultiplyAlpha; + // guarentee an update.. + glTexture.dirtyId = -1; + + texture._glTextures[this.CONTEXT_UID] = glTexture; + + this.managedTextures.push(texture); + texture.on('dispose', this.destroyTexture, this); + + return glTexture; + } + + updateTexture(texture) + { + const glTexture = texture._glTextures[this.CONTEXT_UID]; + const gl = this.gl; + + // TODO there are only 3 textures as far as im aware? + // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) + if(texture.target === gl.TEXTURE_CUBE_MAP) + { + // console.log( gl.UNSIGNED_BYTE) + for (var i = 0; i < texture.sides.length; i++) + { + // TODO - we should only upload what changed.. + // but im sure this is not going to be a problem just yet! + var texturePart = texture.sides[i]; + + if(texturePart.resource) + { + if(texturePart.resource.uploadable) + { + + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.format, + texture.type, + texturePart.resource.source); + } + else + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.width, + texture.height, + 0, + texture.format, + texture.type, + texturePart.resource.source); + } + } + else + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.width, + texture.height, + 0, + texture.format, + texture.type, + null); + } + } + } + else + { + if(texture.resource) + { + if(texture.resource.uploadable) + { + glTexture.upload(texture.resource.source); + + } + else + { + glTexture.uploadData(texture.resource.source, texture.width, texture.height); + } + } + else + { + glTexture.uploadData(null, texture.width, texture.height); + } + } + + // lets only update what changes.. + this.setStyle(texture); + } + + /** + * Deletes the texture from WebGL + * + * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy + * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) + { + texture = texture.baseTexture || texture; + + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + this.unbind(texture); + + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + const i = this.managedTextures.indexOf(texture); + + if (i !== -1) + { + removeItems(this.managedTextures, i, 1); + } + } + } + } + + setStyle(texture) + { + const gl = this.gl; + + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); + + if(texture.mipmap) + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } + + gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } +} \ No newline at end of file diff --git a/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js b/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js index 8326e63..3544cb9 100644 --- a/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js +++ b/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js @@ -11,8 +11,6 @@ export default function checkMaxIfStatmentsInShader(maxIfs, gl) { - const createTempContext = !gl; - // @if DEBUG if (maxIfs === 0) { @@ -20,16 +18,6 @@ } // @endif - if (createTempContext) - { - const tinyCanvas = document.createElement('canvas'); - - tinyCanvas.width = 1; - tinyCanvas.height = 1; - - gl = glCore.createContext(tinyCanvas); - } - const shader = gl.createShader(gl.FRAGMENT_SHADER); while (true) // eslint-disable-line no-constant-condition @@ -50,15 +38,6 @@ } } - if (createTempContext) - { - // get rid of context - if (gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').loseContext(); - } - } - return maxIfs; } diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index 5fe92ad..c0b538b 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -2,6 +2,7 @@ import generateUniformsSync from './generateUniformsSync'; import glCore from 'pixi-gl-core'; import { ProgramCache } from '../utils'; +import getTestContext from '../utils/getTestContext'; let UID = 0; @@ -53,7 +54,7 @@ */ extractData(vertexSrc, fragmentSrc) { - const gl = glCore._testingContext || Program.getTestingContext(); + const gl = getTestContext(); if (!gl) { @@ -202,36 +203,6 @@ } /** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ - static getTestingContext() - { - try - { - if (!Program.testingContext) - { - const canvas = document.createElement('canvas'); - - canvas.width = 1; - canvas.height = 1; - - Program.testingContext = glCore.createContext(canvas); - } - } - - catch (e) - { - // eslint-disable-line no-empty - } - - return Program.testingContext; - } - - /** * A short hand function to create a program based of a vertex and fragment shader * this method will also check to see if there is a cached program. * diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js new file mode 100644 index 0000000..313f078 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js @@ -0,0 +1,41 @@ + +var mapType = require('./mapType'); +var mapSize = require('./mapSize'); + +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the attributes from + * @return attributes {Object} + */ +var extractAttributes = function(gl, program) +{ + var attributes = {}; + + var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + + for (var i = 0; i < totalAttributes; i++) + { + var attribData = gl.getActiveAttrib(program, i); + var type = mapType(gl, attribData.type); + + attributes[attribData.name] = { + type:type, + size:mapSize(type), + location:gl.getAttribLocation(program, attribData.name), + //TODO - make an attribute object + pointer: pointer + }; + } + + return attributes; +}; + +var pointer = function(type, normalized, stride, start){ + // console.log(this.location) + gl.vertexAttribPointer(this.location,this.size, type || gl.FLOAT, normalized || false, stride || 0, start || 0); +}; + +module.exports = extractAttributes; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js new file mode 100644 index 0000000..78d9f0d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js @@ -0,0 +1,35 @@ +var mapType = require('./mapType'); +var defaultValue = require('./defaultValue'); + +/** + * Extracts the uniforms + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the uniforms from + * @return uniforms {Object} + */ +var extractUniforms = function(gl, program) +{ + var uniforms = {}; + + var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + + for (var i = 0; i < totalUniforms; i++) + { + var uniformData = gl.getActiveUniform(program, i); + var name = uniformData.name.replace(/\[.*?\]/, ""); + var type = mapType(gl, uniformData.type ); + + uniforms[name] = { + type:type, + size:uniformData.size, + location:gl.getUniformLocation(program, name), + value:defaultValue(type, uniformData.size) + }; + } + + return uniforms; +}; + +module.exports = extractUniforms; diff --git a/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js new file mode 100644 index 0000000..c2aa597 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js @@ -0,0 +1,144 @@ +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param uniforms {Array} @mat ? + * @return attributes {Object} + */ +var generateUniformAccessObject = function(gl, uniformData) +{ + // this is the object we will be sending back. + // an object hierachy will be created for structs + var uniforms = {data:{}}; + + uniforms.gl = gl; + + var uniformKeys= Object.keys(uniformData); + + for (var i = 0; i < uniformKeys.length; i++) + { + var fullName = uniformKeys[i]; + + var nameTokens = fullName.split('.'); + var name = nameTokens[nameTokens.length - 1]; + + + var uniformGroup = getUniformGroup(nameTokens, uniforms); + + var uniform = uniformData[fullName]; + uniformGroup.data[name] = uniform; + + uniformGroup.gl = gl; + + Object.defineProperty(uniformGroup, name, { + get: generateGetter(name), + set: generateSetter(name, uniform) + }); + } + + return uniforms; +}; + +var generateGetter = function(name) +{ + var template = getterTemplate.replace('%%', name); + return new Function(template); // jshint ignore:line +}; + +var generateSetter = function(name, uniform) +{ + var template = setterTemplate.replace(/%%/g, name); + var setTemplate; + + if(uniform.size === 1) + { + setTemplate = GLSL_TO_SINGLE_SETTERS[uniform.type]; + } + else + { + setTemplate = GLSL_TO_ARRAY_SETTERS[uniform.type]; + } + + if(setTemplate) + { + template += "\nthis.gl." + setTemplate + ";"; + } + + return new Function('value', template); // jshint ignore:line +}; + +var getUniformGroup = function(nameTokens, uniform) +{ + var cur = uniform; + + for (var i = 0; i < nameTokens.length - 1; i++) + { + var o = cur[nameTokens[i]] || {data:{}}; + cur[nameTokens[i]] = o; + cur = o; + } + + return cur; +}; + +var getterTemplate = [ + 'return this.data.%%.value;', +].join('\n'); + +var setterTemplate = [ + 'this.data.%%.value = value;', + 'var location = this.data.%%.location;' +].join('\n'); + + +var GLSL_TO_SINGLE_SETTERS = { + + 'float': 'uniform1f(location, value)', + + 'vec2': 'uniform2f(location, value[0], value[1])', + 'vec3': 'uniform3f(location, value[0], value[1], value[2])', + 'vec4': 'uniform4f(location, value[0], value[1], value[2], value[3])', + + 'int': 'uniform1i(location, value)', + 'ivec2': 'uniform2i(location, value[0], value[1])', + 'ivec3': 'uniform3i(location, value[0], value[1], value[2])', + 'ivec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'bool': 'uniform1i(location, value)', + 'bvec2': 'uniform2i(location, value[0], value[1])', + 'bvec3': 'uniform3i(location, value[0], value[1], value[2])', + 'bvec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'mat2': 'uniformMatrix2fv(location, false, value)', + 'mat3': 'uniformMatrix3fv(location, false, value)', + 'mat4': 'uniformMatrix4fv(location, false, value)', + + 'sampler2D':'uniform1i(location, value)', + 'samplerCube': 'uniform1i(location, value)' + +}; + +var GLSL_TO_ARRAY_SETTERS = { + + 'float': 'uniform1fv(location, value)', + + 'vec2': 'uniform2fv(location, value)', + 'vec3': 'uniform3fv(location, value)', + 'vec4': 'uniform4fv(location, value)', + + 'int': 'uniform1iv(location, value)', + 'ivec2': 'uniform2iv(location, value)', + 'ivec3': 'uniform3iv(location, value)', + 'ivec4': 'uniform4iv(location, value)', + + 'bool': 'uniform1iv(location, value)', + 'bvec2': 'uniform2iv(location, value)', + 'bvec3': 'uniform3iv(location, value)', + 'bvec4': 'uniform4iv(location, value)', + + 'sampler2D':'uniform1iv(location, value)', + 'samplerCube': 'uniform1i(location, value)' +}; + +module.exports = generateUniformAccessObject; diff --git a/src/core/renderers/webgl/systems/shader/shader/index.js b/src/core/renderers/webgl/systems/shader/shader/index.js new file mode 100644 index 0000000..e08684d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/index.js @@ -0,0 +1,10 @@ +module.exports = { + compileProgram: require('./compileProgram'), + defaultValue: require('./defaultValue'), + extractAttributes: require('./extractAttributes'), + extractUniforms: require('./extractUniforms'), + generateUniformAccessObject: require('./generateUniformAccessObject'), + setPrecision: require('./setPrecision'), + mapSize: require('./mapSize'), + mapType: require('./mapType') +}; \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/shader/shader/mapSize.js b/src/core/renderers/webgl/systems/shader/shader/mapSize.js new file mode 100644 index 0000000..61d9b36 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapSize.js @@ -0,0 +1,36 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +var mapSize = function(type) +{ + return GLSL_TO_SIZE[type]; +}; + + +var GLSL_TO_SIZE = { + 'float': 1, + 'vec2': 2, + 'vec3': 3, + 'vec4': 4, + + 'int': 1, + 'ivec2': 2, + 'ivec3': 3, + 'ivec4': 4, + + 'bool': 1, + 'bvec2': 2, + 'bvec3': 3, + 'bvec4': 4, + + 'mat2': 4, + 'mat3': 9, + 'mat4': 16, + + 'sampler2D': 1 +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js new file mode 100644 index 0000000..581dace --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -0,0 +1,47 @@ + + +var mapSize = function(gl, type) +{ + if(!GL_TABLE) + { + var typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for(var i = 0; i < typeNames.length; ++i) + { + var tn = typeNames[i]; + GL_TABLE[ gl[tn] ] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +}; + +var GL_TABLE = null; + +var GL_TO_GLSL_TYPES = { + 'FLOAT': 'float', + 'FLOAT_VEC2': 'vec2', + 'FLOAT_VEC3': 'vec3', + 'FLOAT_VEC4': 'vec4', + + 'INT': 'int', + 'INT_VEC2': 'ivec2', + 'INT_VEC3': 'ivec3', + 'INT_VEC4': 'ivec4', + + 'BOOL': 'bool', + 'BOOL_VEC2': 'bvec2', + 'BOOL_VEC3': 'bvec3', + 'BOOL_VEC4': 'bvec4', + + 'FLOAT_MAT2': 'mat2', + 'FLOAT_MAT3': 'mat3', + 'FLOAT_MAT4': 'mat4', + + 'SAMPLER_2D': 'sampler2D', + 'SAMPLER_CUBE': 'samplerCube' +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/setPrecision.js b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js new file mode 100644 index 0000000..383459a --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +var setPrecision = function(src, precision) +{ + if(src.substring(0, 9) !== 'precision') + { + return 'precision ' + precision + ' float;\n' + src; + } + + return src; +}; + +module.exports = setPrecision; diff --git a/src/core/renderers/webgl/systems/textures/GLTexture.js b/src/core/renderers/webgl/systems/textures/GLTexture.js new file mode 100644 index 0000000..cb8f6ad --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/GLTexture.js @@ -0,0 +1,333 @@ + +/** + * Helper class to create a WebGL Texture + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL context + * @param width {number} the width of the texture + * @param height {number} the height of the texture + * @param format {number} the pixel format of the texture. defaults to gl.RGBA + * @param type {number} the gl type of the texture. defaults to gl.UNSIGNED_BYTE + */ +var Texture = function(gl, width, height, format, type) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + + /** + * The WebGL texture + * + * @member {WebGLTexture} + */ + this.texture = gl.createTexture(); + + /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() + * + * @member {Boolean} + */ + // some settings.. + this.mipmap = false; + + + /** + * Set to true to enable pre-multiplied alpha + * + * @member {Boolean} + */ + this.premultiplyAlpha = false; + + /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || gl.RGBA; + + /** + * The gl type of the texture. defaults to gl.UNSIGNED_BYTE + * + * @member {Number} + */ + this.type = type || gl.UNSIGNED_BYTE; + + +}; + +/** + * Uploads this texture to the GPU + * @param source {HTMLImageElement|ImageData|HTMLVideoElement} the source image of the texture + */ +Texture.prototype.upload = function(source) +{ + this.bind(); + + var gl = this.gl; + + + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + var newWidth = source.videoWidth || source.width; + var newHeight = source.videoHeight || source.height; + + if(newHeight !== this.height || newWidth !== this.width) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.format, this.type, source); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.format, this.type, source); + } + + // if the source is a video, we need to use the videoWidth / videoHeight properties as width / height will be incorrect. + this.width = newWidth; + this.height = newHeight; + +}; + +var FLOATING_POINT_AVAILABLE = false; + +/** + * Use a data source and uploads this texture to the GPU + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.prototype.uploadData = function(data, width, height) +{ + this.bind(); + + var gl = this.gl; + + + if(data instanceof Float32Array) + { + if(!FLOATING_POINT_AVAILABLE) + { + var ext = gl.getExtension("OES_texture_float"); + + if(ext) + { + FLOATING_POINT_AVAILABLE = true; + } + else + { + throw new Error('floating point textures not available'); + } + } + + this.type = gl.FLOAT; + } + else + { + // TODO support for other types + this.type = this.type || gl.UNSIGNED_BYTE; + } + + // what type of data? + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + + if(width !== this.width || height !== this.height) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, this.type, data || null); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, this.format, this.type, data || null); + } + + this.width = width; + this.height = height; + + +// texSubImage2D +}; + +/** + * Binds the texture + * @param location + */ +Texture.prototype.bind = function(location) +{ + var gl = this.gl; + + if(location !== undefined) + { + gl.activeTexture(gl.TEXTURE0 + location); + } + + gl.bindTexture(gl.TEXTURE_2D, this.texture); +}; + +/** + * Unbinds the texture + */ +Texture.prototype.unbind = function() +{ + var gl = this.gl; + gl.bindTexture(gl.TEXTURE_2D, null); +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.minFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + if(this.mipmap) + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR : gl.NEAREST); + } +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.magFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, linear ? gl.LINEAR : gl.NEAREST); +}; + +/** + * Enables mipmapping + */ +Texture.prototype.enableMipmap = function() +{ + var gl = this.gl; + + this.bind(); + + this.mipmap = true; + + gl.generateMipmap(gl.TEXTURE_2D); +}; + +/** + * Enables linear filtering + */ +Texture.prototype.enableLinearScaling = function() +{ + this.minFilter(true); + this.magFilter(true); +}; + +/** + * Enables nearest neighbour interpolation + */ +Texture.prototype.enableNearestScaling = function() +{ + this.minFilter(false); + this.magFilter(false); +}; + +/** + * Enables clamping on the texture so WebGL will not repeat it + */ +Texture.prototype.enableWrapClamp = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); +}; + +/** + * Enable tiling on the texture + */ +Texture.prototype.enableWrapRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); +}; + +Texture.prototype.enableWrapMirrorRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT); +}; + + +/** + * Destroys this texture + */ +Texture.prototype.destroy = function() +{ + var gl = this.gl; + //TODO + gl.deleteTexture(this.texture); +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param source {HTMLImageElement|ImageData} the source image of the texture + * @param premultiplyAlpha {Boolean} If we want to use pre-multiplied alpha + */ +Texture.fromSource = function(gl, source, premultiplyAlpha) +{ + var texture = new Texture(gl); + texture.premultiplyAlpha = premultiplyAlpha || false; + texture.upload(source); + + return texture; +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.fromData = function(gl, data, width, height) +{ + //console.log(data, width, height); + var texture = new Texture(gl); + texture.uploadData(data, width, height); + + return texture; +}; + + +module.exports = Texture; diff --git a/src/core/renderers/webgl/systems/textures/TextureGCSystem.js b/src/core/renderers/webgl/systems/textures/TextureGCSystem.js new file mode 100644 index 0000000..aff6b01 --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/TextureGCSystem.js @@ -0,0 +1,111 @@ +import WebGLSystem from '../WebGLSystem'; +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 TextureGCSystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; + 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.frameBuffer && 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.textureSystem; + + // 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/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js new file mode 100644 index 0000000..8416cec --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -0,0 +1,283 @@ +import WebGLSystem from '../WebGLSystem'; +import GLTexture from './GLTexture'; +import { removeItems } from '../../../../utils'; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ +export default class TextureSystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO set to max textures... + this.boundTextures = [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ]; + + this.currentLocation = -1; + + this.managedTextures = []; + + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // TODO move this.. to a nice make empty textures class.. + this.emptyTextures = {} + + this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); + this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); + + gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); + + for (var i = 0; i < 6; i++) + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + + 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) + { + + const gl = this.gl; + + + location = location || 0; + + if(this.currentLocation !== location) + { + this.currentLocation = location; + gl.activeTexture(gl.TEXTURE0 + location); + } + + if(texture) + { + texture = texture.baseTexture || texture; + + if(texture.valid) + { + + 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); + this.boundTextures[location] = null; + } + } + + unbind(texture) + { + const gl = this.gl; + + for (var i = 0; i < this.boundTextures.length; i++) { + + if(this.boundTextures[i] === texture) + { + if(this.currentLocation !== i) + { + gl.activeTexture(gl.TEXTURE0 + i); + this.currentLocation = i; + } + + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); + this.boundTextures[i] = null; + } + } + } + + initTexture(texture) + { + const gl = this.gl; + + var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); + glTexture.premultiplyAlpha = texture.premultiplyAlpha; + // guarentee an update.. + glTexture.dirtyId = -1; + + texture._glTextures[this.CONTEXT_UID] = glTexture; + + this.managedTextures.push(texture); + texture.on('dispose', this.destroyTexture, this); + + return glTexture; + } + + updateTexture(texture) + { + const glTexture = texture._glTextures[this.CONTEXT_UID]; + const gl = this.gl; + + // TODO there are only 3 textures as far as im aware? + // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) + if(texture.target === gl.TEXTURE_CUBE_MAP) + { + // console.log( gl.UNSIGNED_BYTE) + for (var i = 0; i < texture.sides.length; i++) + { + // TODO - we should only upload what changed.. + // but im sure this is not going to be a problem just yet! + var texturePart = texture.sides[i]; + + if(texturePart.resource) + { + if(texturePart.resource.uploadable) + { + + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.format, + texture.type, + texturePart.resource.source); + } + else + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.width, + texture.height, + 0, + texture.format, + texture.type, + texturePart.resource.source); + } + } + else + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.width, + texture.height, + 0, + texture.format, + texture.type, + null); + } + } + } + else + { + if(texture.resource) + { + if(texture.resource.uploadable) + { + glTexture.upload(texture.resource.source); + + } + else + { + glTexture.uploadData(texture.resource.source, texture.width, texture.height); + } + } + else + { + glTexture.uploadData(null, texture.width, texture.height); + } + } + + // lets only update what changes.. + this.setStyle(texture); + } + + /** + * Deletes the texture from WebGL + * + * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy + * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) + { + texture = texture.baseTexture || texture; + + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + this.unbind(texture); + + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + const i = this.managedTextures.indexOf(texture); + + if (i !== -1) + { + removeItems(this.managedTextures, i, 1); + } + } + } + } + + setStyle(texture) + { + const gl = this.gl; + + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); + + if(texture.mipmap) + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } + + gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } +} \ No newline at end of file diff --git a/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js b/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js index 8326e63..3544cb9 100644 --- a/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js +++ b/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js @@ -11,8 +11,6 @@ export default function checkMaxIfStatmentsInShader(maxIfs, gl) { - const createTempContext = !gl; - // @if DEBUG if (maxIfs === 0) { @@ -20,16 +18,6 @@ } // @endif - if (createTempContext) - { - const tinyCanvas = document.createElement('canvas'); - - tinyCanvas.width = 1; - tinyCanvas.height = 1; - - gl = glCore.createContext(tinyCanvas); - } - const shader = gl.createShader(gl.FRAGMENT_SHADER); while (true) // eslint-disable-line no-constant-condition @@ -50,15 +38,6 @@ } } - if (createTempContext) - { - // get rid of context - if (gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').loseContext(); - } - } - return maxIfs; } diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index 5fe92ad..c0b538b 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -2,6 +2,7 @@ import generateUniformsSync from './generateUniformsSync'; import glCore from 'pixi-gl-core'; import { ProgramCache } from '../utils'; +import getTestContext from '../utils/getTestContext'; let UID = 0; @@ -53,7 +54,7 @@ */ extractData(vertexSrc, fragmentSrc) { - const gl = glCore._testingContext || Program.getTestingContext(); + const gl = getTestContext(); if (!gl) { @@ -202,36 +203,6 @@ } /** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ - static getTestingContext() - { - try - { - if (!Program.testingContext) - { - const canvas = document.createElement('canvas'); - - canvas.width = 1; - canvas.height = 1; - - Program.testingContext = glCore.createContext(canvas); - } - } - - catch (e) - { - // eslint-disable-line no-empty - } - - return Program.testingContext; - } - - /** * A short hand function to create a program based of a vertex and fragment shader * this method will also check to see if there is a cached program. * diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 22df803..1341ec3 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -1,11 +1,11 @@ import ObjectRenderer from '../../renderers/webgl/utils/ObjectRenderer'; import WebGLRenderer from '../../renderers/webgl/WebGLRenderer'; +import GLBuffer from '../../renderers/webgl/systems/geometry/GLBuffer'; import createIndicesForQuads from '../../utils/createIndicesForQuads'; import generateMultiTextureShader from './generateMultiTextureShader'; import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; -import glCore from 'pixi-gl-core'; import bitTwiddle from 'bit-twiddle'; let TICK = 0; @@ -115,7 +115,6 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } - glCore._testingContext = gl; const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); const sampleValues = new Int32Array(this.MAX_TEXTURES); @@ -128,7 +127,7 @@ shader.uniformGroup.add('default', {uSamplers:sampleValues}, true);//this.renderer.globalUniforms; shader.uniforms.globals = this.renderer.globalUniforms; - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.indexBuffer = GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. @@ -137,7 +136,7 @@ for (let i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); /* eslint-disable max-len */ var attributeData = shader.program.attributeData; @@ -389,7 +388,7 @@ if (this.vaoMax <= this.vertexCount) { this.vaoMax++; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); /* eslint-disable max-len */ diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js new file mode 100644 index 0000000..313f078 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js @@ -0,0 +1,41 @@ + +var mapType = require('./mapType'); +var mapSize = require('./mapSize'); + +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the attributes from + * @return attributes {Object} + */ +var extractAttributes = function(gl, program) +{ + var attributes = {}; + + var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + + for (var i = 0; i < totalAttributes; i++) + { + var attribData = gl.getActiveAttrib(program, i); + var type = mapType(gl, attribData.type); + + attributes[attribData.name] = { + type:type, + size:mapSize(type), + location:gl.getAttribLocation(program, attribData.name), + //TODO - make an attribute object + pointer: pointer + }; + } + + return attributes; +}; + +var pointer = function(type, normalized, stride, start){ + // console.log(this.location) + gl.vertexAttribPointer(this.location,this.size, type || gl.FLOAT, normalized || false, stride || 0, start || 0); +}; + +module.exports = extractAttributes; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js new file mode 100644 index 0000000..78d9f0d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js @@ -0,0 +1,35 @@ +var mapType = require('./mapType'); +var defaultValue = require('./defaultValue'); + +/** + * Extracts the uniforms + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the uniforms from + * @return uniforms {Object} + */ +var extractUniforms = function(gl, program) +{ + var uniforms = {}; + + var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + + for (var i = 0; i < totalUniforms; i++) + { + var uniformData = gl.getActiveUniform(program, i); + var name = uniformData.name.replace(/\[.*?\]/, ""); + var type = mapType(gl, uniformData.type ); + + uniforms[name] = { + type:type, + size:uniformData.size, + location:gl.getUniformLocation(program, name), + value:defaultValue(type, uniformData.size) + }; + } + + return uniforms; +}; + +module.exports = extractUniforms; diff --git a/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js new file mode 100644 index 0000000..c2aa597 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js @@ -0,0 +1,144 @@ +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param uniforms {Array} @mat ? + * @return attributes {Object} + */ +var generateUniformAccessObject = function(gl, uniformData) +{ + // this is the object we will be sending back. + // an object hierachy will be created for structs + var uniforms = {data:{}}; + + uniforms.gl = gl; + + var uniformKeys= Object.keys(uniformData); + + for (var i = 0; i < uniformKeys.length; i++) + { + var fullName = uniformKeys[i]; + + var nameTokens = fullName.split('.'); + var name = nameTokens[nameTokens.length - 1]; + + + var uniformGroup = getUniformGroup(nameTokens, uniforms); + + var uniform = uniformData[fullName]; + uniformGroup.data[name] = uniform; + + uniformGroup.gl = gl; + + Object.defineProperty(uniformGroup, name, { + get: generateGetter(name), + set: generateSetter(name, uniform) + }); + } + + return uniforms; +}; + +var generateGetter = function(name) +{ + var template = getterTemplate.replace('%%', name); + return new Function(template); // jshint ignore:line +}; + +var generateSetter = function(name, uniform) +{ + var template = setterTemplate.replace(/%%/g, name); + var setTemplate; + + if(uniform.size === 1) + { + setTemplate = GLSL_TO_SINGLE_SETTERS[uniform.type]; + } + else + { + setTemplate = GLSL_TO_ARRAY_SETTERS[uniform.type]; + } + + if(setTemplate) + { + template += "\nthis.gl." + setTemplate + ";"; + } + + return new Function('value', template); // jshint ignore:line +}; + +var getUniformGroup = function(nameTokens, uniform) +{ + var cur = uniform; + + for (var i = 0; i < nameTokens.length - 1; i++) + { + var o = cur[nameTokens[i]] || {data:{}}; + cur[nameTokens[i]] = o; + cur = o; + } + + return cur; +}; + +var getterTemplate = [ + 'return this.data.%%.value;', +].join('\n'); + +var setterTemplate = [ + 'this.data.%%.value = value;', + 'var location = this.data.%%.location;' +].join('\n'); + + +var GLSL_TO_SINGLE_SETTERS = { + + 'float': 'uniform1f(location, value)', + + 'vec2': 'uniform2f(location, value[0], value[1])', + 'vec3': 'uniform3f(location, value[0], value[1], value[2])', + 'vec4': 'uniform4f(location, value[0], value[1], value[2], value[3])', + + 'int': 'uniform1i(location, value)', + 'ivec2': 'uniform2i(location, value[0], value[1])', + 'ivec3': 'uniform3i(location, value[0], value[1], value[2])', + 'ivec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'bool': 'uniform1i(location, value)', + 'bvec2': 'uniform2i(location, value[0], value[1])', + 'bvec3': 'uniform3i(location, value[0], value[1], value[2])', + 'bvec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'mat2': 'uniformMatrix2fv(location, false, value)', + 'mat3': 'uniformMatrix3fv(location, false, value)', + 'mat4': 'uniformMatrix4fv(location, false, value)', + + 'sampler2D':'uniform1i(location, value)', + 'samplerCube': 'uniform1i(location, value)' + +}; + +var GLSL_TO_ARRAY_SETTERS = { + + 'float': 'uniform1fv(location, value)', + + 'vec2': 'uniform2fv(location, value)', + 'vec3': 'uniform3fv(location, value)', + 'vec4': 'uniform4fv(location, value)', + + 'int': 'uniform1iv(location, value)', + 'ivec2': 'uniform2iv(location, value)', + 'ivec3': 'uniform3iv(location, value)', + 'ivec4': 'uniform4iv(location, value)', + + 'bool': 'uniform1iv(location, value)', + 'bvec2': 'uniform2iv(location, value)', + 'bvec3': 'uniform3iv(location, value)', + 'bvec4': 'uniform4iv(location, value)', + + 'sampler2D':'uniform1iv(location, value)', + 'samplerCube': 'uniform1i(location, value)' +}; + +module.exports = generateUniformAccessObject; diff --git a/src/core/renderers/webgl/systems/shader/shader/index.js b/src/core/renderers/webgl/systems/shader/shader/index.js new file mode 100644 index 0000000..e08684d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/index.js @@ -0,0 +1,10 @@ +module.exports = { + compileProgram: require('./compileProgram'), + defaultValue: require('./defaultValue'), + extractAttributes: require('./extractAttributes'), + extractUniforms: require('./extractUniforms'), + generateUniformAccessObject: require('./generateUniformAccessObject'), + setPrecision: require('./setPrecision'), + mapSize: require('./mapSize'), + mapType: require('./mapType') +}; \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/shader/shader/mapSize.js b/src/core/renderers/webgl/systems/shader/shader/mapSize.js new file mode 100644 index 0000000..61d9b36 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapSize.js @@ -0,0 +1,36 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +var mapSize = function(type) +{ + return GLSL_TO_SIZE[type]; +}; + + +var GLSL_TO_SIZE = { + 'float': 1, + 'vec2': 2, + 'vec3': 3, + 'vec4': 4, + + 'int': 1, + 'ivec2': 2, + 'ivec3': 3, + 'ivec4': 4, + + 'bool': 1, + 'bvec2': 2, + 'bvec3': 3, + 'bvec4': 4, + + 'mat2': 4, + 'mat3': 9, + 'mat4': 16, + + 'sampler2D': 1 +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js new file mode 100644 index 0000000..581dace --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -0,0 +1,47 @@ + + +var mapSize = function(gl, type) +{ + if(!GL_TABLE) + { + var typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for(var i = 0; i < typeNames.length; ++i) + { + var tn = typeNames[i]; + GL_TABLE[ gl[tn] ] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +}; + +var GL_TABLE = null; + +var GL_TO_GLSL_TYPES = { + 'FLOAT': 'float', + 'FLOAT_VEC2': 'vec2', + 'FLOAT_VEC3': 'vec3', + 'FLOAT_VEC4': 'vec4', + + 'INT': 'int', + 'INT_VEC2': 'ivec2', + 'INT_VEC3': 'ivec3', + 'INT_VEC4': 'ivec4', + + 'BOOL': 'bool', + 'BOOL_VEC2': 'bvec2', + 'BOOL_VEC3': 'bvec3', + 'BOOL_VEC4': 'bvec4', + + 'FLOAT_MAT2': 'mat2', + 'FLOAT_MAT3': 'mat3', + 'FLOAT_MAT4': 'mat4', + + 'SAMPLER_2D': 'sampler2D', + 'SAMPLER_CUBE': 'samplerCube' +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/setPrecision.js b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js new file mode 100644 index 0000000..383459a --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +var setPrecision = function(src, precision) +{ + if(src.substring(0, 9) !== 'precision') + { + return 'precision ' + precision + ' float;\n' + src; + } + + return src; +}; + +module.exports = setPrecision; diff --git a/src/core/renderers/webgl/systems/textures/GLTexture.js b/src/core/renderers/webgl/systems/textures/GLTexture.js new file mode 100644 index 0000000..cb8f6ad --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/GLTexture.js @@ -0,0 +1,333 @@ + +/** + * Helper class to create a WebGL Texture + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL context + * @param width {number} the width of the texture + * @param height {number} the height of the texture + * @param format {number} the pixel format of the texture. defaults to gl.RGBA + * @param type {number} the gl type of the texture. defaults to gl.UNSIGNED_BYTE + */ +var Texture = function(gl, width, height, format, type) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + + /** + * The WebGL texture + * + * @member {WebGLTexture} + */ + this.texture = gl.createTexture(); + + /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() + * + * @member {Boolean} + */ + // some settings.. + this.mipmap = false; + + + /** + * Set to true to enable pre-multiplied alpha + * + * @member {Boolean} + */ + this.premultiplyAlpha = false; + + /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || gl.RGBA; + + /** + * The gl type of the texture. defaults to gl.UNSIGNED_BYTE + * + * @member {Number} + */ + this.type = type || gl.UNSIGNED_BYTE; + + +}; + +/** + * Uploads this texture to the GPU + * @param source {HTMLImageElement|ImageData|HTMLVideoElement} the source image of the texture + */ +Texture.prototype.upload = function(source) +{ + this.bind(); + + var gl = this.gl; + + + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + var newWidth = source.videoWidth || source.width; + var newHeight = source.videoHeight || source.height; + + if(newHeight !== this.height || newWidth !== this.width) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.format, this.type, source); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.format, this.type, source); + } + + // if the source is a video, we need to use the videoWidth / videoHeight properties as width / height will be incorrect. + this.width = newWidth; + this.height = newHeight; + +}; + +var FLOATING_POINT_AVAILABLE = false; + +/** + * Use a data source and uploads this texture to the GPU + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.prototype.uploadData = function(data, width, height) +{ + this.bind(); + + var gl = this.gl; + + + if(data instanceof Float32Array) + { + if(!FLOATING_POINT_AVAILABLE) + { + var ext = gl.getExtension("OES_texture_float"); + + if(ext) + { + FLOATING_POINT_AVAILABLE = true; + } + else + { + throw new Error('floating point textures not available'); + } + } + + this.type = gl.FLOAT; + } + else + { + // TODO support for other types + this.type = this.type || gl.UNSIGNED_BYTE; + } + + // what type of data? + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + + if(width !== this.width || height !== this.height) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, this.type, data || null); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, this.format, this.type, data || null); + } + + this.width = width; + this.height = height; + + +// texSubImage2D +}; + +/** + * Binds the texture + * @param location + */ +Texture.prototype.bind = function(location) +{ + var gl = this.gl; + + if(location !== undefined) + { + gl.activeTexture(gl.TEXTURE0 + location); + } + + gl.bindTexture(gl.TEXTURE_2D, this.texture); +}; + +/** + * Unbinds the texture + */ +Texture.prototype.unbind = function() +{ + var gl = this.gl; + gl.bindTexture(gl.TEXTURE_2D, null); +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.minFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + if(this.mipmap) + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR : gl.NEAREST); + } +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.magFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, linear ? gl.LINEAR : gl.NEAREST); +}; + +/** + * Enables mipmapping + */ +Texture.prototype.enableMipmap = function() +{ + var gl = this.gl; + + this.bind(); + + this.mipmap = true; + + gl.generateMipmap(gl.TEXTURE_2D); +}; + +/** + * Enables linear filtering + */ +Texture.prototype.enableLinearScaling = function() +{ + this.minFilter(true); + this.magFilter(true); +}; + +/** + * Enables nearest neighbour interpolation + */ +Texture.prototype.enableNearestScaling = function() +{ + this.minFilter(false); + this.magFilter(false); +}; + +/** + * Enables clamping on the texture so WebGL will not repeat it + */ +Texture.prototype.enableWrapClamp = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); +}; + +/** + * Enable tiling on the texture + */ +Texture.prototype.enableWrapRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); +}; + +Texture.prototype.enableWrapMirrorRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT); +}; + + +/** + * Destroys this texture + */ +Texture.prototype.destroy = function() +{ + var gl = this.gl; + //TODO + gl.deleteTexture(this.texture); +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param source {HTMLImageElement|ImageData} the source image of the texture + * @param premultiplyAlpha {Boolean} If we want to use pre-multiplied alpha + */ +Texture.fromSource = function(gl, source, premultiplyAlpha) +{ + var texture = new Texture(gl); + texture.premultiplyAlpha = premultiplyAlpha || false; + texture.upload(source); + + return texture; +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.fromData = function(gl, data, width, height) +{ + //console.log(data, width, height); + var texture = new Texture(gl); + texture.uploadData(data, width, height); + + return texture; +}; + + +module.exports = Texture; diff --git a/src/core/renderers/webgl/systems/textures/TextureGCSystem.js b/src/core/renderers/webgl/systems/textures/TextureGCSystem.js new file mode 100644 index 0000000..aff6b01 --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/TextureGCSystem.js @@ -0,0 +1,111 @@ +import WebGLSystem from '../WebGLSystem'; +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 TextureGCSystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; + 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.frameBuffer && 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.textureSystem; + + // 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/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js new file mode 100644 index 0000000..8416cec --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -0,0 +1,283 @@ +import WebGLSystem from '../WebGLSystem'; +import GLTexture from './GLTexture'; +import { removeItems } from '../../../../utils'; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ +export default class TextureSystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO set to max textures... + this.boundTextures = [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ]; + + this.currentLocation = -1; + + this.managedTextures = []; + + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // TODO move this.. to a nice make empty textures class.. + this.emptyTextures = {} + + this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); + this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); + + gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); + + for (var i = 0; i < 6; i++) + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + + 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) + { + + const gl = this.gl; + + + location = location || 0; + + if(this.currentLocation !== location) + { + this.currentLocation = location; + gl.activeTexture(gl.TEXTURE0 + location); + } + + if(texture) + { + texture = texture.baseTexture || texture; + + if(texture.valid) + { + + 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); + this.boundTextures[location] = null; + } + } + + unbind(texture) + { + const gl = this.gl; + + for (var i = 0; i < this.boundTextures.length; i++) { + + if(this.boundTextures[i] === texture) + { + if(this.currentLocation !== i) + { + gl.activeTexture(gl.TEXTURE0 + i); + this.currentLocation = i; + } + + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); + this.boundTextures[i] = null; + } + } + } + + initTexture(texture) + { + const gl = this.gl; + + var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); + glTexture.premultiplyAlpha = texture.premultiplyAlpha; + // guarentee an update.. + glTexture.dirtyId = -1; + + texture._glTextures[this.CONTEXT_UID] = glTexture; + + this.managedTextures.push(texture); + texture.on('dispose', this.destroyTexture, this); + + return glTexture; + } + + updateTexture(texture) + { + const glTexture = texture._glTextures[this.CONTEXT_UID]; + const gl = this.gl; + + // TODO there are only 3 textures as far as im aware? + // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) + if(texture.target === gl.TEXTURE_CUBE_MAP) + { + // console.log( gl.UNSIGNED_BYTE) + for (var i = 0; i < texture.sides.length; i++) + { + // TODO - we should only upload what changed.. + // but im sure this is not going to be a problem just yet! + var texturePart = texture.sides[i]; + + if(texturePart.resource) + { + if(texturePart.resource.uploadable) + { + + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.format, + texture.type, + texturePart.resource.source); + } + else + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.width, + texture.height, + 0, + texture.format, + texture.type, + texturePart.resource.source); + } + } + else + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.width, + texture.height, + 0, + texture.format, + texture.type, + null); + } + } + } + else + { + if(texture.resource) + { + if(texture.resource.uploadable) + { + glTexture.upload(texture.resource.source); + + } + else + { + glTexture.uploadData(texture.resource.source, texture.width, texture.height); + } + } + else + { + glTexture.uploadData(null, texture.width, texture.height); + } + } + + // lets only update what changes.. + this.setStyle(texture); + } + + /** + * Deletes the texture from WebGL + * + * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy + * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) + { + texture = texture.baseTexture || texture; + + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + this.unbind(texture); + + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + const i = this.managedTextures.indexOf(texture); + + if (i !== -1) + { + removeItems(this.managedTextures, i, 1); + } + } + } + } + + setStyle(texture) + { + const gl = this.gl; + + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); + + if(texture.mipmap) + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } + + gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } +} \ No newline at end of file diff --git a/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js b/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js index 8326e63..3544cb9 100644 --- a/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js +++ b/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js @@ -11,8 +11,6 @@ export default function checkMaxIfStatmentsInShader(maxIfs, gl) { - const createTempContext = !gl; - // @if DEBUG if (maxIfs === 0) { @@ -20,16 +18,6 @@ } // @endif - if (createTempContext) - { - const tinyCanvas = document.createElement('canvas'); - - tinyCanvas.width = 1; - tinyCanvas.height = 1; - - gl = glCore.createContext(tinyCanvas); - } - const shader = gl.createShader(gl.FRAGMENT_SHADER); while (true) // eslint-disable-line no-constant-condition @@ -50,15 +38,6 @@ } } - if (createTempContext) - { - // get rid of context - if (gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').loseContext(); - } - } - return maxIfs; } diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index 5fe92ad..c0b538b 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -2,6 +2,7 @@ import generateUniformsSync from './generateUniformsSync'; import glCore from 'pixi-gl-core'; import { ProgramCache } from '../utils'; +import getTestContext from '../utils/getTestContext'; let UID = 0; @@ -53,7 +54,7 @@ */ extractData(vertexSrc, fragmentSrc) { - const gl = glCore._testingContext || Program.getTestingContext(); + const gl = getTestContext(); if (!gl) { @@ -202,36 +203,6 @@ } /** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ - static getTestingContext() - { - try - { - if (!Program.testingContext) - { - const canvas = document.createElement('canvas'); - - canvas.width = 1; - canvas.height = 1; - - Program.testingContext = glCore.createContext(canvas); - } - } - - catch (e) - { - // eslint-disable-line no-empty - } - - return Program.testingContext; - } - - /** * A short hand function to create a program based of a vertex and fragment shader * this method will also check to see if there is a cached program. * diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 22df803..1341ec3 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -1,11 +1,11 @@ import ObjectRenderer from '../../renderers/webgl/utils/ObjectRenderer'; import WebGLRenderer from '../../renderers/webgl/WebGLRenderer'; +import GLBuffer from '../../renderers/webgl/systems/geometry/GLBuffer'; import createIndicesForQuads from '../../utils/createIndicesForQuads'; import generateMultiTextureShader from './generateMultiTextureShader'; import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; -import glCore from 'pixi-gl-core'; import bitTwiddle from 'bit-twiddle'; let TICK = 0; @@ -115,7 +115,6 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } - glCore._testingContext = gl; const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); const sampleValues = new Int32Array(this.MAX_TEXTURES); @@ -128,7 +127,7 @@ shader.uniformGroup.add('default', {uSamplers:sampleValues}, true);//this.renderer.globalUniforms; shader.uniforms.globals = this.renderer.globalUniforms; - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.indexBuffer = GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. @@ -137,7 +136,7 @@ for (let i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); /* eslint-disable max-len */ var attributeData = shader.program.attributeData; @@ -389,7 +388,7 @@ if (this.vaoMax <= this.vertexCount) { this.vaoMax++; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); /* eslint-disable max-len */ diff --git a/src/core/utils/getTestContext.js b/src/core/utils/getTestContext.js new file mode 100644 index 0000000..40ffe25 --- /dev/null +++ b/src/core/utils/getTestContext.js @@ -0,0 +1,36 @@ + + +let context = null; + + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ + +export default function getTestContext() +{ + if(!context) + { + const canvas = document.createElement('canvas'); + const options = {}; + + canvas.width = 1; + canvas.height = 1; + + + context = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!context) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + return context; +} diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js new file mode 100644 index 0000000..313f078 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js @@ -0,0 +1,41 @@ + +var mapType = require('./mapType'); +var mapSize = require('./mapSize'); + +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the attributes from + * @return attributes {Object} + */ +var extractAttributes = function(gl, program) +{ + var attributes = {}; + + var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + + for (var i = 0; i < totalAttributes; i++) + { + var attribData = gl.getActiveAttrib(program, i); + var type = mapType(gl, attribData.type); + + attributes[attribData.name] = { + type:type, + size:mapSize(type), + location:gl.getAttribLocation(program, attribData.name), + //TODO - make an attribute object + pointer: pointer + }; + } + + return attributes; +}; + +var pointer = function(type, normalized, stride, start){ + // console.log(this.location) + gl.vertexAttribPointer(this.location,this.size, type || gl.FLOAT, normalized || false, stride || 0, start || 0); +}; + +module.exports = extractAttributes; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js new file mode 100644 index 0000000..78d9f0d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js @@ -0,0 +1,35 @@ +var mapType = require('./mapType'); +var defaultValue = require('./defaultValue'); + +/** + * Extracts the uniforms + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the uniforms from + * @return uniforms {Object} + */ +var extractUniforms = function(gl, program) +{ + var uniforms = {}; + + var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + + for (var i = 0; i < totalUniforms; i++) + { + var uniformData = gl.getActiveUniform(program, i); + var name = uniformData.name.replace(/\[.*?\]/, ""); + var type = mapType(gl, uniformData.type ); + + uniforms[name] = { + type:type, + size:uniformData.size, + location:gl.getUniformLocation(program, name), + value:defaultValue(type, uniformData.size) + }; + } + + return uniforms; +}; + +module.exports = extractUniforms; diff --git a/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js new file mode 100644 index 0000000..c2aa597 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js @@ -0,0 +1,144 @@ +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param uniforms {Array} @mat ? + * @return attributes {Object} + */ +var generateUniformAccessObject = function(gl, uniformData) +{ + // this is the object we will be sending back. + // an object hierachy will be created for structs + var uniforms = {data:{}}; + + uniforms.gl = gl; + + var uniformKeys= Object.keys(uniformData); + + for (var i = 0; i < uniformKeys.length; i++) + { + var fullName = uniformKeys[i]; + + var nameTokens = fullName.split('.'); + var name = nameTokens[nameTokens.length - 1]; + + + var uniformGroup = getUniformGroup(nameTokens, uniforms); + + var uniform = uniformData[fullName]; + uniformGroup.data[name] = uniform; + + uniformGroup.gl = gl; + + Object.defineProperty(uniformGroup, name, { + get: generateGetter(name), + set: generateSetter(name, uniform) + }); + } + + return uniforms; +}; + +var generateGetter = function(name) +{ + var template = getterTemplate.replace('%%', name); + return new Function(template); // jshint ignore:line +}; + +var generateSetter = function(name, uniform) +{ + var template = setterTemplate.replace(/%%/g, name); + var setTemplate; + + if(uniform.size === 1) + { + setTemplate = GLSL_TO_SINGLE_SETTERS[uniform.type]; + } + else + { + setTemplate = GLSL_TO_ARRAY_SETTERS[uniform.type]; + } + + if(setTemplate) + { + template += "\nthis.gl." + setTemplate + ";"; + } + + return new Function('value', template); // jshint ignore:line +}; + +var getUniformGroup = function(nameTokens, uniform) +{ + var cur = uniform; + + for (var i = 0; i < nameTokens.length - 1; i++) + { + var o = cur[nameTokens[i]] || {data:{}}; + cur[nameTokens[i]] = o; + cur = o; + } + + return cur; +}; + +var getterTemplate = [ + 'return this.data.%%.value;', +].join('\n'); + +var setterTemplate = [ + 'this.data.%%.value = value;', + 'var location = this.data.%%.location;' +].join('\n'); + + +var GLSL_TO_SINGLE_SETTERS = { + + 'float': 'uniform1f(location, value)', + + 'vec2': 'uniform2f(location, value[0], value[1])', + 'vec3': 'uniform3f(location, value[0], value[1], value[2])', + 'vec4': 'uniform4f(location, value[0], value[1], value[2], value[3])', + + 'int': 'uniform1i(location, value)', + 'ivec2': 'uniform2i(location, value[0], value[1])', + 'ivec3': 'uniform3i(location, value[0], value[1], value[2])', + 'ivec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'bool': 'uniform1i(location, value)', + 'bvec2': 'uniform2i(location, value[0], value[1])', + 'bvec3': 'uniform3i(location, value[0], value[1], value[2])', + 'bvec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'mat2': 'uniformMatrix2fv(location, false, value)', + 'mat3': 'uniformMatrix3fv(location, false, value)', + 'mat4': 'uniformMatrix4fv(location, false, value)', + + 'sampler2D':'uniform1i(location, value)', + 'samplerCube': 'uniform1i(location, value)' + +}; + +var GLSL_TO_ARRAY_SETTERS = { + + 'float': 'uniform1fv(location, value)', + + 'vec2': 'uniform2fv(location, value)', + 'vec3': 'uniform3fv(location, value)', + 'vec4': 'uniform4fv(location, value)', + + 'int': 'uniform1iv(location, value)', + 'ivec2': 'uniform2iv(location, value)', + 'ivec3': 'uniform3iv(location, value)', + 'ivec4': 'uniform4iv(location, value)', + + 'bool': 'uniform1iv(location, value)', + 'bvec2': 'uniform2iv(location, value)', + 'bvec3': 'uniform3iv(location, value)', + 'bvec4': 'uniform4iv(location, value)', + + 'sampler2D':'uniform1iv(location, value)', + 'samplerCube': 'uniform1i(location, value)' +}; + +module.exports = generateUniformAccessObject; diff --git a/src/core/renderers/webgl/systems/shader/shader/index.js b/src/core/renderers/webgl/systems/shader/shader/index.js new file mode 100644 index 0000000..e08684d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/index.js @@ -0,0 +1,10 @@ +module.exports = { + compileProgram: require('./compileProgram'), + defaultValue: require('./defaultValue'), + extractAttributes: require('./extractAttributes'), + extractUniforms: require('./extractUniforms'), + generateUniformAccessObject: require('./generateUniformAccessObject'), + setPrecision: require('./setPrecision'), + mapSize: require('./mapSize'), + mapType: require('./mapType') +}; \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/shader/shader/mapSize.js b/src/core/renderers/webgl/systems/shader/shader/mapSize.js new file mode 100644 index 0000000..61d9b36 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapSize.js @@ -0,0 +1,36 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +var mapSize = function(type) +{ + return GLSL_TO_SIZE[type]; +}; + + +var GLSL_TO_SIZE = { + 'float': 1, + 'vec2': 2, + 'vec3': 3, + 'vec4': 4, + + 'int': 1, + 'ivec2': 2, + 'ivec3': 3, + 'ivec4': 4, + + 'bool': 1, + 'bvec2': 2, + 'bvec3': 3, + 'bvec4': 4, + + 'mat2': 4, + 'mat3': 9, + 'mat4': 16, + + 'sampler2D': 1 +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js new file mode 100644 index 0000000..581dace --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -0,0 +1,47 @@ + + +var mapSize = function(gl, type) +{ + if(!GL_TABLE) + { + var typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for(var i = 0; i < typeNames.length; ++i) + { + var tn = typeNames[i]; + GL_TABLE[ gl[tn] ] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +}; + +var GL_TABLE = null; + +var GL_TO_GLSL_TYPES = { + 'FLOAT': 'float', + 'FLOAT_VEC2': 'vec2', + 'FLOAT_VEC3': 'vec3', + 'FLOAT_VEC4': 'vec4', + + 'INT': 'int', + 'INT_VEC2': 'ivec2', + 'INT_VEC3': 'ivec3', + 'INT_VEC4': 'ivec4', + + 'BOOL': 'bool', + 'BOOL_VEC2': 'bvec2', + 'BOOL_VEC3': 'bvec3', + 'BOOL_VEC4': 'bvec4', + + 'FLOAT_MAT2': 'mat2', + 'FLOAT_MAT3': 'mat3', + 'FLOAT_MAT4': 'mat4', + + 'SAMPLER_2D': 'sampler2D', + 'SAMPLER_CUBE': 'samplerCube' +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/setPrecision.js b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js new file mode 100644 index 0000000..383459a --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +var setPrecision = function(src, precision) +{ + if(src.substring(0, 9) !== 'precision') + { + return 'precision ' + precision + ' float;\n' + src; + } + + return src; +}; + +module.exports = setPrecision; diff --git a/src/core/renderers/webgl/systems/textures/GLTexture.js b/src/core/renderers/webgl/systems/textures/GLTexture.js new file mode 100644 index 0000000..cb8f6ad --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/GLTexture.js @@ -0,0 +1,333 @@ + +/** + * Helper class to create a WebGL Texture + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL context + * @param width {number} the width of the texture + * @param height {number} the height of the texture + * @param format {number} the pixel format of the texture. defaults to gl.RGBA + * @param type {number} the gl type of the texture. defaults to gl.UNSIGNED_BYTE + */ +var Texture = function(gl, width, height, format, type) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + + /** + * The WebGL texture + * + * @member {WebGLTexture} + */ + this.texture = gl.createTexture(); + + /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() + * + * @member {Boolean} + */ + // some settings.. + this.mipmap = false; + + + /** + * Set to true to enable pre-multiplied alpha + * + * @member {Boolean} + */ + this.premultiplyAlpha = false; + + /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || gl.RGBA; + + /** + * The gl type of the texture. defaults to gl.UNSIGNED_BYTE + * + * @member {Number} + */ + this.type = type || gl.UNSIGNED_BYTE; + + +}; + +/** + * Uploads this texture to the GPU + * @param source {HTMLImageElement|ImageData|HTMLVideoElement} the source image of the texture + */ +Texture.prototype.upload = function(source) +{ + this.bind(); + + var gl = this.gl; + + + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + var newWidth = source.videoWidth || source.width; + var newHeight = source.videoHeight || source.height; + + if(newHeight !== this.height || newWidth !== this.width) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.format, this.type, source); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.format, this.type, source); + } + + // if the source is a video, we need to use the videoWidth / videoHeight properties as width / height will be incorrect. + this.width = newWidth; + this.height = newHeight; + +}; + +var FLOATING_POINT_AVAILABLE = false; + +/** + * Use a data source and uploads this texture to the GPU + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.prototype.uploadData = function(data, width, height) +{ + this.bind(); + + var gl = this.gl; + + + if(data instanceof Float32Array) + { + if(!FLOATING_POINT_AVAILABLE) + { + var ext = gl.getExtension("OES_texture_float"); + + if(ext) + { + FLOATING_POINT_AVAILABLE = true; + } + else + { + throw new Error('floating point textures not available'); + } + } + + this.type = gl.FLOAT; + } + else + { + // TODO support for other types + this.type = this.type || gl.UNSIGNED_BYTE; + } + + // what type of data? + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + + if(width !== this.width || height !== this.height) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, this.type, data || null); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, this.format, this.type, data || null); + } + + this.width = width; + this.height = height; + + +// texSubImage2D +}; + +/** + * Binds the texture + * @param location + */ +Texture.prototype.bind = function(location) +{ + var gl = this.gl; + + if(location !== undefined) + { + gl.activeTexture(gl.TEXTURE0 + location); + } + + gl.bindTexture(gl.TEXTURE_2D, this.texture); +}; + +/** + * Unbinds the texture + */ +Texture.prototype.unbind = function() +{ + var gl = this.gl; + gl.bindTexture(gl.TEXTURE_2D, null); +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.minFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + if(this.mipmap) + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR : gl.NEAREST); + } +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.magFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, linear ? gl.LINEAR : gl.NEAREST); +}; + +/** + * Enables mipmapping + */ +Texture.prototype.enableMipmap = function() +{ + var gl = this.gl; + + this.bind(); + + this.mipmap = true; + + gl.generateMipmap(gl.TEXTURE_2D); +}; + +/** + * Enables linear filtering + */ +Texture.prototype.enableLinearScaling = function() +{ + this.minFilter(true); + this.magFilter(true); +}; + +/** + * Enables nearest neighbour interpolation + */ +Texture.prototype.enableNearestScaling = function() +{ + this.minFilter(false); + this.magFilter(false); +}; + +/** + * Enables clamping on the texture so WebGL will not repeat it + */ +Texture.prototype.enableWrapClamp = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); +}; + +/** + * Enable tiling on the texture + */ +Texture.prototype.enableWrapRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); +}; + +Texture.prototype.enableWrapMirrorRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT); +}; + + +/** + * Destroys this texture + */ +Texture.prototype.destroy = function() +{ + var gl = this.gl; + //TODO + gl.deleteTexture(this.texture); +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param source {HTMLImageElement|ImageData} the source image of the texture + * @param premultiplyAlpha {Boolean} If we want to use pre-multiplied alpha + */ +Texture.fromSource = function(gl, source, premultiplyAlpha) +{ + var texture = new Texture(gl); + texture.premultiplyAlpha = premultiplyAlpha || false; + texture.upload(source); + + return texture; +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.fromData = function(gl, data, width, height) +{ + //console.log(data, width, height); + var texture = new Texture(gl); + texture.uploadData(data, width, height); + + return texture; +}; + + +module.exports = Texture; diff --git a/src/core/renderers/webgl/systems/textures/TextureGCSystem.js b/src/core/renderers/webgl/systems/textures/TextureGCSystem.js new file mode 100644 index 0000000..aff6b01 --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/TextureGCSystem.js @@ -0,0 +1,111 @@ +import WebGLSystem from '../WebGLSystem'; +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 TextureGCSystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; + 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.frameBuffer && 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.textureSystem; + + // 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/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js new file mode 100644 index 0000000..8416cec --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -0,0 +1,283 @@ +import WebGLSystem from '../WebGLSystem'; +import GLTexture from './GLTexture'; +import { removeItems } from '../../../../utils'; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ +export default class TextureSystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO set to max textures... + this.boundTextures = [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ]; + + this.currentLocation = -1; + + this.managedTextures = []; + + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // TODO move this.. to a nice make empty textures class.. + this.emptyTextures = {} + + this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); + this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); + + gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); + + for (var i = 0; i < 6; i++) + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + + 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) + { + + const gl = this.gl; + + + location = location || 0; + + if(this.currentLocation !== location) + { + this.currentLocation = location; + gl.activeTexture(gl.TEXTURE0 + location); + } + + if(texture) + { + texture = texture.baseTexture || texture; + + if(texture.valid) + { + + 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); + this.boundTextures[location] = null; + } + } + + unbind(texture) + { + const gl = this.gl; + + for (var i = 0; i < this.boundTextures.length; i++) { + + if(this.boundTextures[i] === texture) + { + if(this.currentLocation !== i) + { + gl.activeTexture(gl.TEXTURE0 + i); + this.currentLocation = i; + } + + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); + this.boundTextures[i] = null; + } + } + } + + initTexture(texture) + { + const gl = this.gl; + + var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); + glTexture.premultiplyAlpha = texture.premultiplyAlpha; + // guarentee an update.. + glTexture.dirtyId = -1; + + texture._glTextures[this.CONTEXT_UID] = glTexture; + + this.managedTextures.push(texture); + texture.on('dispose', this.destroyTexture, this); + + return glTexture; + } + + updateTexture(texture) + { + const glTexture = texture._glTextures[this.CONTEXT_UID]; + const gl = this.gl; + + // TODO there are only 3 textures as far as im aware? + // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) + if(texture.target === gl.TEXTURE_CUBE_MAP) + { + // console.log( gl.UNSIGNED_BYTE) + for (var i = 0; i < texture.sides.length; i++) + { + // TODO - we should only upload what changed.. + // but im sure this is not going to be a problem just yet! + var texturePart = texture.sides[i]; + + if(texturePart.resource) + { + if(texturePart.resource.uploadable) + { + + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.format, + texture.type, + texturePart.resource.source); + } + else + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.width, + texture.height, + 0, + texture.format, + texture.type, + texturePart.resource.source); + } + } + else + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.width, + texture.height, + 0, + texture.format, + texture.type, + null); + } + } + } + else + { + if(texture.resource) + { + if(texture.resource.uploadable) + { + glTexture.upload(texture.resource.source); + + } + else + { + glTexture.uploadData(texture.resource.source, texture.width, texture.height); + } + } + else + { + glTexture.uploadData(null, texture.width, texture.height); + } + } + + // lets only update what changes.. + this.setStyle(texture); + } + + /** + * Deletes the texture from WebGL + * + * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy + * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) + { + texture = texture.baseTexture || texture; + + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + this.unbind(texture); + + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + const i = this.managedTextures.indexOf(texture); + + if (i !== -1) + { + removeItems(this.managedTextures, i, 1); + } + } + } + } + + setStyle(texture) + { + const gl = this.gl; + + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); + + if(texture.mipmap) + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } + + gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } +} \ No newline at end of file diff --git a/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js b/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js index 8326e63..3544cb9 100644 --- a/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js +++ b/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js @@ -11,8 +11,6 @@ export default function checkMaxIfStatmentsInShader(maxIfs, gl) { - const createTempContext = !gl; - // @if DEBUG if (maxIfs === 0) { @@ -20,16 +18,6 @@ } // @endif - if (createTempContext) - { - const tinyCanvas = document.createElement('canvas'); - - tinyCanvas.width = 1; - tinyCanvas.height = 1; - - gl = glCore.createContext(tinyCanvas); - } - const shader = gl.createShader(gl.FRAGMENT_SHADER); while (true) // eslint-disable-line no-constant-condition @@ -50,15 +38,6 @@ } } - if (createTempContext) - { - // get rid of context - if (gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').loseContext(); - } - } - return maxIfs; } diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index 5fe92ad..c0b538b 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -2,6 +2,7 @@ import generateUniformsSync from './generateUniformsSync'; import glCore from 'pixi-gl-core'; import { ProgramCache } from '../utils'; +import getTestContext from '../utils/getTestContext'; let UID = 0; @@ -53,7 +54,7 @@ */ extractData(vertexSrc, fragmentSrc) { - const gl = glCore._testingContext || Program.getTestingContext(); + const gl = getTestContext(); if (!gl) { @@ -202,36 +203,6 @@ } /** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ - static getTestingContext() - { - try - { - if (!Program.testingContext) - { - const canvas = document.createElement('canvas'); - - canvas.width = 1; - canvas.height = 1; - - Program.testingContext = glCore.createContext(canvas); - } - } - - catch (e) - { - // eslint-disable-line no-empty - } - - return Program.testingContext; - } - - /** * A short hand function to create a program based of a vertex and fragment shader * this method will also check to see if there is a cached program. * diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 22df803..1341ec3 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -1,11 +1,11 @@ import ObjectRenderer from '../../renderers/webgl/utils/ObjectRenderer'; import WebGLRenderer from '../../renderers/webgl/WebGLRenderer'; +import GLBuffer from '../../renderers/webgl/systems/geometry/GLBuffer'; import createIndicesForQuads from '../../utils/createIndicesForQuads'; import generateMultiTextureShader from './generateMultiTextureShader'; import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; -import glCore from 'pixi-gl-core'; import bitTwiddle from 'bit-twiddle'; let TICK = 0; @@ -115,7 +115,6 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } - glCore._testingContext = gl; const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); const sampleValues = new Int32Array(this.MAX_TEXTURES); @@ -128,7 +127,7 @@ shader.uniformGroup.add('default', {uSamplers:sampleValues}, true);//this.renderer.globalUniforms; shader.uniforms.globals = this.renderer.globalUniforms; - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.indexBuffer = GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. @@ -137,7 +136,7 @@ for (let i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); /* eslint-disable max-len */ var attributeData = shader.program.attributeData; @@ -389,7 +388,7 @@ if (this.vaoMax <= this.vertexCount) { this.vaoMax++; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); /* eslint-disable max-len */ diff --git a/src/core/utils/getTestContext.js b/src/core/utils/getTestContext.js new file mode 100644 index 0000000..40ffe25 --- /dev/null +++ b/src/core/utils/getTestContext.js @@ -0,0 +1,36 @@ + + +let context = null; + + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ + +export default function getTestContext() +{ + if(!context) + { + const canvas = document.createElement('canvas'); + const options = {}; + + canvas.width = 1; + canvas.height = 1; + + + context = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!context) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + return context; +} diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index f741797..78d24c3 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -1,5 +1,4 @@ import * as core from '../../core'; -import glCore from 'pixi-gl-core'; const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; diff --git a/npm-debug.log.3977192692 b/npm-debug.log.3977192692 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/npm-debug.log.3977192692 diff --git a/package.json b/package.json index a134818..d35cbb2 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", - "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 967ca78..03c4384 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -1,4 +1,3 @@ -import glCore from 'pixi-gl-core'; import Geometry from '../../geometry/Geometry'; import Buffer from '../../geometry/Buffer'; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 92cbab2..e1cf7cd 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -4,17 +4,16 @@ import FilterSystem from './systems/FilterSystem'; import FramebufferSystem from './systems/FramebufferSystem'; import RenderTextureSystem from './systems/RenderTextureSystem'; -import NewTextureSystem from './systems/NewTextureSystem'; -import TextureSystem from './TextureManager'; +import TextureSystem from './systems/textures/TextureSystem'; import ProjectionSystem from './systems/ProjectionSystem'; import StateSystem from './systems/StateSystem'; -import GeometrySystem from './systems/GeometrySystem'; -import ShaderSystem from './systems/ShaderSystem'; +import GeometrySystem from './systems/geometry/GeometrySystem'; +import ShaderSystem from './systems/shader/ShaderSystem'; import ContextSystem from './systems/ContextSystem'; import BatchSystem from './systems/BatchSystem'; -import TextureGCSystem from './systems/TextureGCSystem'; +import TextureGCSystem from './systems/textures/TextureGCSystem'; import { pluginTarget } from '../../utils'; -import glCore from 'pixi-gl-core'; +import VertexArrayObject from './systems/geometry/VertexArrayObject'; import { RENDERER_TYPE } from '../../const'; import UniformGroup from '../../shader/UniformGroup'; import { Rectangle, Matrix } from '../../math'; @@ -75,7 +74,7 @@ if (this.legacy) { - glCore.VertexArrayObject.FORCE_NATIVE = true; + VertexArrayObject.FORCE_NATIVE = true; } // runners! @@ -106,7 +105,7 @@ .addSystem(ContextSystem) .addSystem(StateSystem) .addSystem(ShaderSystem) - .addSystem(NewTextureSystem, 'texture') + .addSystem(TextureSystem, 'texture') .addSystem(GeometrySystem) .addSystem(FramebufferSystem) .addSystem(StencilSystem) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index eb6f498..ff958c7 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -1,6 +1,5 @@ import WebGLSystem from './WebGLSystem'; import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; let CONTEXT_UID = 0; @@ -42,12 +41,6 @@ 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; @@ -66,11 +59,35 @@ initFromOptions(options) { - const gl = glCore.createContext(this.renderer.view, options); + const gl = this.createContext(this.renderer.view, options); this.initFromContext(gl); } /** + * Helper class to create a webGL Context + * + * @class + * @memberof PIXI.glCore + * @param canvas {HTMLCanvasElement} the canvas element that we will get the context from + * @param options {Object} An options object that gets passed in to the canvas element containing the context attributes, + * see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext for the options available + * @return {WebGLRenderingContext} the WebGL context + */ + createContext(canvas, options) + { + var gl = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!gl) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + + return gl; + }; + + /** * Handles a lost webgl context * * @private diff --git a/src/core/renderers/webgl/systems/GeometrySystem.js b/src/core/renderers/webgl/systems/GeometrySystem.js deleted file mode 100644 index c934aa6..0000000 --- a/src/core/renderers/webgl/systems/GeometrySystem.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { Rectangle, Matrix } from '../../../math'; -import glCore from 'pixi-gl-core'; - - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ - -export default class GeometrySystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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/systems/NewTextureSystem.js b/src/core/renderers/webgl/systems/NewTextureSystem.js deleted file mode 100644 index bf113ce..0000000 --- a/src/core/renderers/webgl/systems/NewTextureSystem.js +++ /dev/null @@ -1,283 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -import { GLFramebuffer, GLTexture } from 'pixi-gl-core'; -import { removeItems } from '../../../utils'; - -/** - * @class - * @extends PIXI.WebGLSystem - * @memberof PIXI - */ -export default class TextureSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. - */ - constructor(renderer) - { - super(renderer); - - // TODO set to max textures... - this.boundTextures = [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ]; - - this.currentLocation = -1; - - this.managedTextures = []; - - } - - /** - * Sets up the renderer context and necessary buffers. - * - * @private - */ - contextChange() - { - const gl = this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // TODO move this.. to a nice make empty textures class.. - this.emptyTextures = {} - - this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); - this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); - - for (var i = 0; i < 6; i++) - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - 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) - { - - const gl = this.gl; - - - location = location || 0; - - if(this.currentLocation !== location) - { - this.currentLocation = location; - gl.activeTexture(gl.TEXTURE0 + location); - } - - if(texture) - { - texture = texture.baseTexture || texture; - - if(texture.valid) - { - - 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); - this.boundTextures[location] = null; - } - } - - unbind(texture) - { - const gl = this.gl; - - for (var i = 0; i < this.boundTextures.length; i++) { - - if(this.boundTextures[i] === texture) - { - if(this.currentLocation !== i) - { - gl.activeTexture(gl.TEXTURE0 + i); - this.currentLocation = i; - } - - gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); - this.boundTextures[i] = null; - } - } - } - - initTexture(texture) - { - const gl = this.gl; - - var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); - glTexture.premultiplyAlpha = texture.premultiplyAlpha; - // guarentee an update.. - glTexture.dirtyId = -1; - - texture._glTextures[this.CONTEXT_UID] = glTexture; - - this.managedTextures.push(texture); - texture.on('dispose', this.destroyTexture, this); - - return glTexture; - } - - updateTexture(texture) - { - const glTexture = texture._glTextures[this.CONTEXT_UID]; - const gl = this.gl; - - // TODO there are only 3 textures as far as im aware? - // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) - if(texture.target === gl.TEXTURE_CUBE_MAP) - { - // console.log( gl.UNSIGNED_BYTE) - for (var i = 0; i < texture.sides.length; i++) - { - // TODO - we should only upload what changed.. - // but im sure this is not going to be a problem just yet! - var texturePart = texture.sides[i]; - - if(texturePart.resource) - { - if(texturePart.resource.uploadable) - { - - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.format, - texture.type, - texturePart.resource.source); - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - texturePart.resource.source); - } - } - else - { - gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, - 0, - texture.format, - texture.width, - texture.height, - 0, - texture.format, - texture.type, - null); - } - } - } - else - { - if(texture.resource) - { - if(texture.resource.uploadable) - { - glTexture.upload(texture.resource.source); - - } - else - { - glTexture.uploadData(texture.resource.source, texture.width, texture.height); - } - } - else - { - glTexture.uploadData(null, texture.width, texture.height); - } - } - - // lets only update what changes.. - this.setStyle(texture); - } - - /** - * Deletes the texture from WebGL - * - * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy - * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. - */ - destroyTexture(texture, skipRemove) - { - texture = texture.baseTexture || texture; - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - this.unbind(texture); - - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('dispose', this.destroyTexture, this); - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - const i = this.managedTextures.indexOf(texture); - - if (i !== -1) - { - removeItems(this.managedTextures, i, 1); - } - } - } - } - - setStyle(texture) - { - const gl = this.gl; - - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); - gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); - - if(texture.mipmap) - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); - } - else - { - gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } - - gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); - } -} \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/ShaderSystem.js b/src/core/renderers/webgl/systems/ShaderSystem.js deleted file mode 100644 index 6844bd2..0000000 --- a/src/core/renderers/webgl/systems/ShaderSystem.js +++ /dev/null @@ -1,165 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 ShaderSystem extends WebGLSystem -{ - /** - * @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 System and removes all its textures - */ - destroy() - { - // TODO implement destroy method for ShaderSystem - this.destroyed = true; - } -} diff --git a/src/core/renderers/webgl/systems/TextureGCSystem.js b/src/core/renderers/webgl/systems/TextureGCSystem.js deleted file mode 100644 index 4cc7dc2..0000000 --- a/src/core/renderers/webgl/systems/TextureGCSystem.js +++ /dev/null @@ -1,111 +0,0 @@ -import WebGLSystem from './WebGLSystem'; -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 TextureGCSystem extends WebGLSystem -{ - /** - * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; - 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.frameBuffer && 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.textureSystem; - - // 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/systems/geometry/GLBuffer.js b/src/core/renderers/webgl/systems/geometry/GLBuffer.js new file mode 100644 index 0000000..876b29e --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GLBuffer.js @@ -0,0 +1,119 @@ +var EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); + +/** + * Helper class to create a webGL buffer + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param type {gl.ARRAY_BUFFER | gl.ELEMENT_ARRAY_BUFFER} @mat + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data + * @param drawType {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ +var Buffer = function(gl, type, data, drawType) +{ + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * The WebGL buffer, created upon instantiation + * + * @member {WebGLBuffer} + */ + this.buffer = gl.createBuffer(); + + /** + * The type of the buffer + * + * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} + */ + this.type = type || gl.ARRAY_BUFFER; + + /** + * The draw type of the buffer + * + * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} + */ + this.drawType = drawType || gl.STATIC_DRAW; + + /** + * The data in the buffer, as a typed array + * + * @member {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} + */ + this.data = EMPTY_ARRAY_BUFFER; + + if(data) + { + this.upload(data); + } + + this._updateID = 0; +}; + +/** + * Uploads the buffer to the GPU + * @param data {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} an array of data to upload + * @param offset {Number} if only a subset of the data should be uploaded, this is the amount of data to subtract + * @param dontBind {Boolean} whether to bind the buffer before uploading it + */ +Buffer.prototype.upload = function(data, offset, dontBind) +{ + // todo - needed? + if(!dontBind) this.bind(); + + var gl = this.gl; + + data = data || this.data; + offset = offset || 0; + + if(this.data.byteLength >= data.byteLength) + { + gl.bufferSubData(this.type, offset, data); + } + else + { + gl.bufferData(this.type, data, this.drawType); + } + + this.data = data; +}; +/** + * Binds the buffer + * + */ +Buffer.prototype.bind = function() +{ + var gl = this.gl; + gl.bindBuffer(this.type, this.buffer); +}; + +Buffer.createVertexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ARRAY_BUFFER, data, drawType); +}; + +Buffer.createIndexBuffer = function(gl, data, drawType) +{ + return new Buffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); +}; + +Buffer.create = function(gl, type, data, drawType) +{ + return new Buffer(gl, type, data, drawType); +}; + +/** + * Destroys the buffer + * + */ +Buffer.prototype.destroy = function(){ + this.gl.deleteBuffer(this.buffer); +}; + +module.exports = Buffer; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js new file mode 100644 index 0000000..837ba94 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -0,0 +1,215 @@ +import WebGLSystem from '../WebGLSystem'; +import { Rectangle, Matrix } from '../../../../math'; +import VertexArrayObject from './VertexArrayObject'; +import GLBuffer from './GLBuffer'; +import setVertexAttribArrays from './setVertexAttribArrays'; + + +const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ + +export default class GeometrySystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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 + */ + 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 + * @return {PIXI.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] = GLBuffer.createIndexBuffer(gl, buffer.data); + } + else + { + /* eslint-disable max-len */ + buffer._glBuffers[this.CONTEXT_UID] = 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 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/systems/geometry/VertexArrayObject.js b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js new file mode 100644 index 0000000..db89556 --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/VertexArrayObject.js @@ -0,0 +1,294 @@ + +// state object// +var setVertexAttribArrays = require( './setVertexAttribArrays' ); + + +/** + * Helper class to work with WebGL VertexArrayObjects (vaos) + * Only works if WebGL extensions are enabled (they usually are) + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL rendering context + */ +function VertexArrayObject(gl, state) +{ + this.nativeVaoExtension = null; + + if(!VertexArrayObject.FORCE_NATIVE) + { + this.nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + this.instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + } + + this.nativeState = state; + + if(this.nativeVaoExtension) + { + this.nativeVao = this.nativeVaoExtension.createVertexArrayOES(); + + var maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + // VAO - overwrite the state.. + this.nativeState = { + tempAttribState: new Array(maxAttribs), + attribState: new Array(maxAttribs) + }; + } + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + /** + * An array of attributes + * + * @member {Array} + */ + this.attributes = []; + + /** + * @member {PIXI.glCore.GLBuffer} + */ + this.indexBuffer = null; + + /** + * A boolean flag + * + * @member {Boolean} + */ + this.dirty = false; +} + +VertexArrayObject.prototype.constructor = VertexArrayObject; +module.exports = VertexArrayObject; + +/** +* Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) +* If you find on older devices that things have gone a bit weird then set this to true. +*/ +/** + * Lets the VAO know if you should use the WebGL extension or the native methods. + * Some devices behave a bit funny when using the newer extensions (im looking at you ipad 2!) + * If you find on older devices that things have gone a bit weird then set this to true. + * @static + * @property {Boolean} FORCE_NATIVE + */ +VertexArrayObject.FORCE_NATIVE = false; + +/** + * Binds the buffer + */ +VertexArrayObject.prototype.bind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + + if(this.dirty) + { + this.dirty = false; + this.activate(); + } + } + else + { + + this.activate(); + } + + return this; +}; + +/** + * Unbinds the buffer + */ +VertexArrayObject.prototype.unbind = function() +{ + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + return this; +}; + +/** + * Uses this vao + */ +VertexArrayObject.prototype.activate = function() +{ + + var gl = this.gl; + var lastBuffer = null; + + for (var i = 0; i < this.attributes.length; i++) + { + var attrib = this.attributes[i]; + + if(lastBuffer !== attrib.buffer) + { + attrib.buffer.bind(); + lastBuffer = attrib.buffer; + } + + gl.vertexAttribPointer(attrib.attribute.location, + attrib.attribute.size, + attrib.type || gl.FLOAT, + attrib.normalized || false, + attrib.stride || 0, + attrib.start || 0); + + if(attrib.instance) + { + if(this.instanceExt) + { + this.instanceExt.vertexAttribDivisorANGLE(attrib.attribute.location, 1); + } + else + { + console.warn('instancing not supported by this device :/') + } + } + } + + setVertexAttribArrays(gl, this.attributes, this.nativeState); + + if(this.indexBuffer) + { + this.indexBuffer.bind(); + } + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + * @param attribute {*} + * @param type {String} + * @param normalized {Boolean} + * @param stride {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.addAttribute = function(buffer, attribute, type, normalized, stride, start, instance) +{ + this.attributes.push({ + buffer: buffer, + attribute: attribute, + + location: attribute.location, + type: type || this.gl.FLOAT, + normalized: normalized || false, + stride: stride || 0, + start: start || 0, + instance: instance + }); + + this.instancedMesh = this.instancedMesh || instance; + + this.dirty = true; + + return this; +}; + +/** + * + * @param buffer {PIXI.gl.GLBuffer} + */ +VertexArrayObject.prototype.addIndex = function(buffer/*, options*/) +{ + this.indexBuffer = buffer; + + this.dirty = true; + + return this; +}; + +/** + * Unbinds this vao and disables it + */ +VertexArrayObject.prototype.clear = function() +{ + // var gl = this.gl; + + // TODO - should this function unbind after clear? + // for now, no but lets see what happens in the real world! + if(this.nativeVao) + { + this.nativeVaoExtension.bindVertexArrayOES(this.nativeVao); + } + + this.attributes.length = 0; + this.indexBuffer = null; + + return this; +}; + +/** + * @param type {Number} + * @param size {Number} + * @param start {Number} + */ +VertexArrayObject.prototype.draw = function(type, size, start, instanceCount) +{ + var gl = this.gl; + + if(this.indexBuffer) + { + if(this.instancedMesh) + { + this.instanceExt.drawElementsInstancedANGLE(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || this.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(this.instancedMesh) + { + // TODO need a better way to calculate size.. + this.instanceExt.drawArrayInstancedANGLE(type, start, size || this.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || this.getSize()); + } + } + + return this; +}; + +/** + * Destroy this vao + */ +VertexArrayObject.prototype.destroy = function() +{ + // lose references + this.gl = null; + this.indexBuffer = null; + this.attributes = null; + this.nativeState = null; + + if(this.nativeVao) + { + this.nativeVaoExtension.deleteVertexArrayOES(this.nativeVao); + } + + this.nativeVaoExtension = null; + this.nativeVao = null; +}; + +VertexArrayObject.prototype.getSize = function() +{ + var attrib = this.attributes[0]; + return attrib.buffer.data.length / (( attrib.stride/4 ) || attrib.attribute.size); +}; diff --git a/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js new file mode 100644 index 0000000..4bc437a --- /dev/null +++ b/src/core/renderers/webgl/systems/geometry/setVertexAttribArrays.js @@ -0,0 +1,55 @@ +// var GL_MAP = {}; + +/** + * @param gl {WebGLRenderingContext} The current WebGL context + * @param attribs {*} + * @param state {*} + */ +var setVertexAttribArrays = function (gl, attribs, state) +{ + var i; + if(state) + { + var tempAttribState = state.tempAttribState, + attribState = state.attribState; + + for (i = 0; i < tempAttribState.length; i++) + { + tempAttribState[i] = false; + } + + // set the new attribs + for (i = 0; i < attribs.length; i++) + { + tempAttribState[attribs[i].attribute.location] = true; + } + + for (i = 0; i < attribState.length; i++) + { + if (attribState[i] !== tempAttribState[i]) + { + attribState[i] = tempAttribState[i]; + + if (state.attribState[i]) + { + gl.enableVertexAttribArray(i); + } + else + { + gl.disableVertexAttribArray(i); + } + } + } + + } + else + { + for (i = 0; i < attribs.length; i++) + { + var attrib = attribs[i]; + gl.enableVertexAttribArray(attrib.attribute.location); + } + } +}; + +module.exports = setVertexAttribArrays; diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js new file mode 100644 index 0000000..1344166 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -0,0 +1,92 @@ + +var compileProgram = require('./shader/compileProgram'), + extractAttributes = require('./shader/extractAttributes'), + extractUniforms = require('./shader/extractUniforms'), + setPrecision = require('./shader/setPrecision'), + generateUniformAccessObject = require('./shader/generateUniformAccessObject'); + +/** + * Helper class to create a webGL Shader + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param precision {string} The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * @param attributeLocations {object} A key value pair showing which location eact attribute should sit eg {position:0, uvs:1} + */ +var Shader = function(gl, vertexSrc, fragmentSrc, precision, attributeLocations) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + if(precision) + { + vertexSrc = setPrecision(vertexSrc, precision); + fragmentSrc = setPrecision(fragmentSrc, precision); + } + + /** + * The shader program + * + * @member {WebGLProgram} + */ + // First compile the program.. + this.program = compileProgram(gl, vertexSrc, fragmentSrc, attributeLocations); + + /** + * The attributes of the shader as an object containing the following properties + * { + * type, + * size, + * location, + * pointer + * } + * @member {Object} + */ + // next extract the attributes + this.attributes = extractAttributes(gl, this.program); + + this.uniformData = extractUniforms(gl, this.program); + + /** + * The uniforms of the shader as an object containing the following properties + * { + * gl, + * data + * } + * @member {Object} + */ + this.uniforms = generateUniformAccessObject( gl, this.uniformData ); + + this.uniformGroups = {}; +}; +/** + * Uses this shader + */ +Shader.prototype.bind = function() +{ + this.gl.useProgram(this.program); +}; + +/** + * Destroys this shader + * TODO + */ +Shader.prototype.destroy = function() +{ + this.attributes = null; + this.uniformData = null; + this.uniforms = null; + + var gl = this.gl; + gl.deleteProgram(this.program); +}; + + +module.exports = Shader; diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js new file mode 100644 index 0000000..eb7af23 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -0,0 +1,165 @@ +import WebGLSystem from '../WebGLSystem'; +import GLShader from './GLShader'; +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 ShaderSystem extends WebGLSystem +{ + /** + * @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 System and removes all its textures + */ + destroy() + { + // TODO implement destroy method for ShaderSystem + this.destroyed = true; + } +} diff --git a/src/core/renderers/webgl/systems/shader/shader/compileProgram.js b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js new file mode 100644 index 0000000..cefca34 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/compileProgram.js @@ -0,0 +1,80 @@ + +/** + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. + * @param attributeLocations {Object} An attribute location map that lets you manually set the attribute locations + * @return {WebGLProgram} the shader program + */ +var compileProgram = function(gl, vertexSrc, fragmentSrc, attributeLocations) +{ + var glVertShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc); + var glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc); + + var program = gl.createProgram(); + + gl.attachShader(program, glVertShader); + gl.attachShader(program, glFragShader); + + // optionally, set the attributes manually for the program rather than letting WebGL decide.. + if(attributeLocations) + { + for(var i in attributeLocations) + { + gl.bindAttribLocation(program, attributeLocations[i], i); + } + } + + + gl.linkProgram(program); + + // if linking fails, then log and cleanup + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + { + console.error('Pixi.js Error: Could not initialize shader.'); + console.error('gl.VALIDATE_STATUS', gl.getProgramParameter(program, gl.VALIDATE_STATUS)); + console.error('gl.getError()', gl.getError()); + + // if there is a program info log, log it + if (gl.getProgramInfoLog(program) !== '') + { + console.warn('Pixi.js Warning: gl.getProgramInfoLog()', gl.getProgramInfoLog(program)); + } + + gl.deleteProgram(program); + program = null; + } + + // clean up some shaders + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + + return program; +}; + +/** + * @private + * @param gl {WebGLRenderingContext} The current WebGL context {WebGLProgram} + * @param type {Number} the type, can be either VERTEX_SHADER or FRAGMENT_SHADER + * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. + * @return {WebGLShader} the shader + */ +var compileShader = function (gl, type, src) +{ + var shader = gl.createShader(type); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + { + console.log(gl.getShaderInfoLog(shader)); + return null; + } + + return shader; +}; + +module.exports = compileProgram; diff --git a/src/core/renderers/webgl/systems/shader/shader/defaultValue.js b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js new file mode 100644 index 0000000..1c57353 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/defaultValue.js @@ -0,0 +1,78 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} Type of value + * @param size {Number} + */ +var defaultValue = function(type, size) +{ + switch (type) + { + case 'float': + return 0; + + case 'vec2': + return new Float32Array(2 * size); + + case 'vec3': + return new Float32Array(3 * size); + + case 'vec4': + return new Float32Array(4 * size); + + case 'int': + case 'sampler2D': + return 0; + + case 'ivec2': + return new Int32Array(2 * size); + + case 'ivec3': + return new Int32Array(3 * size); + + case 'ivec4': + return new Int32Array(4 * size); + + case 'bool': + return false; + + case 'bvec2': + + return booleanArray( 2 * size); + + case 'bvec3': + return booleanArray(3 * size); + + case 'bvec4': + return booleanArray(4 * size); + + case 'mat2': + return new Float32Array([1, 0, + 0, 1]); + + case 'mat3': + return new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + + case 'mat4': + return new Float32Array([1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]); + } +}; + +var booleanArray = function(size) +{ + var array = new Array(size); + + for (var i = 0; i < array.length; i++) + { + array[i] = false; + } + + return array; +}; + +module.exports = defaultValue; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js new file mode 100644 index 0000000..313f078 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractAttributes.js @@ -0,0 +1,41 @@ + +var mapType = require('./mapType'); +var mapSize = require('./mapSize'); + +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the attributes from + * @return attributes {Object} + */ +var extractAttributes = function(gl, program) +{ + var attributes = {}; + + var totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + + for (var i = 0; i < totalAttributes; i++) + { + var attribData = gl.getActiveAttrib(program, i); + var type = mapType(gl, attribData.type); + + attributes[attribData.name] = { + type:type, + size:mapSize(type), + location:gl.getAttribLocation(program, attribData.name), + //TODO - make an attribute object + pointer: pointer + }; + } + + return attributes; +}; + +var pointer = function(type, normalized, stride, start){ + // console.log(this.location) + gl.vertexAttribPointer(this.location,this.size, type || gl.FLOAT, normalized || false, stride || 0, start || 0); +}; + +module.exports = extractAttributes; diff --git a/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js new file mode 100644 index 0000000..78d9f0d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/extractUniforms.js @@ -0,0 +1,35 @@ +var mapType = require('./mapType'); +var defaultValue = require('./defaultValue'); + +/** + * Extracts the uniforms + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param program {WebGLProgram} The shader program to get the uniforms from + * @return uniforms {Object} + */ +var extractUniforms = function(gl, program) +{ + var uniforms = {}; + + var totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + + for (var i = 0; i < totalUniforms; i++) + { + var uniformData = gl.getActiveUniform(program, i); + var name = uniformData.name.replace(/\[.*?\]/, ""); + var type = mapType(gl, uniformData.type ); + + uniforms[name] = { + type:type, + size:uniformData.size, + location:gl.getUniformLocation(program, name), + value:defaultValue(type, uniformData.size) + }; + } + + return uniforms; +}; + +module.exports = extractUniforms; diff --git a/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js new file mode 100644 index 0000000..c2aa597 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/generateUniformAccessObject.js @@ -0,0 +1,144 @@ +/** + * Extracts the attributes + * @class + * @memberof PIXI.glCore.shader + * @param gl {WebGLRenderingContext} The current WebGL rendering context + * @param uniforms {Array} @mat ? + * @return attributes {Object} + */ +var generateUniformAccessObject = function(gl, uniformData) +{ + // this is the object we will be sending back. + // an object hierachy will be created for structs + var uniforms = {data:{}}; + + uniforms.gl = gl; + + var uniformKeys= Object.keys(uniformData); + + for (var i = 0; i < uniformKeys.length; i++) + { + var fullName = uniformKeys[i]; + + var nameTokens = fullName.split('.'); + var name = nameTokens[nameTokens.length - 1]; + + + var uniformGroup = getUniformGroup(nameTokens, uniforms); + + var uniform = uniformData[fullName]; + uniformGroup.data[name] = uniform; + + uniformGroup.gl = gl; + + Object.defineProperty(uniformGroup, name, { + get: generateGetter(name), + set: generateSetter(name, uniform) + }); + } + + return uniforms; +}; + +var generateGetter = function(name) +{ + var template = getterTemplate.replace('%%', name); + return new Function(template); // jshint ignore:line +}; + +var generateSetter = function(name, uniform) +{ + var template = setterTemplate.replace(/%%/g, name); + var setTemplate; + + if(uniform.size === 1) + { + setTemplate = GLSL_TO_SINGLE_SETTERS[uniform.type]; + } + else + { + setTemplate = GLSL_TO_ARRAY_SETTERS[uniform.type]; + } + + if(setTemplate) + { + template += "\nthis.gl." + setTemplate + ";"; + } + + return new Function('value', template); // jshint ignore:line +}; + +var getUniformGroup = function(nameTokens, uniform) +{ + var cur = uniform; + + for (var i = 0; i < nameTokens.length - 1; i++) + { + var o = cur[nameTokens[i]] || {data:{}}; + cur[nameTokens[i]] = o; + cur = o; + } + + return cur; +}; + +var getterTemplate = [ + 'return this.data.%%.value;', +].join('\n'); + +var setterTemplate = [ + 'this.data.%%.value = value;', + 'var location = this.data.%%.location;' +].join('\n'); + + +var GLSL_TO_SINGLE_SETTERS = { + + 'float': 'uniform1f(location, value)', + + 'vec2': 'uniform2f(location, value[0], value[1])', + 'vec3': 'uniform3f(location, value[0], value[1], value[2])', + 'vec4': 'uniform4f(location, value[0], value[1], value[2], value[3])', + + 'int': 'uniform1i(location, value)', + 'ivec2': 'uniform2i(location, value[0], value[1])', + 'ivec3': 'uniform3i(location, value[0], value[1], value[2])', + 'ivec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'bool': 'uniform1i(location, value)', + 'bvec2': 'uniform2i(location, value[0], value[1])', + 'bvec3': 'uniform3i(location, value[0], value[1], value[2])', + 'bvec4': 'uniform4i(location, value[0], value[1], value[2], value[3])', + + 'mat2': 'uniformMatrix2fv(location, false, value)', + 'mat3': 'uniformMatrix3fv(location, false, value)', + 'mat4': 'uniformMatrix4fv(location, false, value)', + + 'sampler2D':'uniform1i(location, value)', + 'samplerCube': 'uniform1i(location, value)' + +}; + +var GLSL_TO_ARRAY_SETTERS = { + + 'float': 'uniform1fv(location, value)', + + 'vec2': 'uniform2fv(location, value)', + 'vec3': 'uniform3fv(location, value)', + 'vec4': 'uniform4fv(location, value)', + + 'int': 'uniform1iv(location, value)', + 'ivec2': 'uniform2iv(location, value)', + 'ivec3': 'uniform3iv(location, value)', + 'ivec4': 'uniform4iv(location, value)', + + 'bool': 'uniform1iv(location, value)', + 'bvec2': 'uniform2iv(location, value)', + 'bvec3': 'uniform3iv(location, value)', + 'bvec4': 'uniform4iv(location, value)', + + 'sampler2D':'uniform1iv(location, value)', + 'samplerCube': 'uniform1i(location, value)' +}; + +module.exports = generateUniformAccessObject; diff --git a/src/core/renderers/webgl/systems/shader/shader/index.js b/src/core/renderers/webgl/systems/shader/shader/index.js new file mode 100644 index 0000000..e08684d --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/index.js @@ -0,0 +1,10 @@ +module.exports = { + compileProgram: require('./compileProgram'), + defaultValue: require('./defaultValue'), + extractAttributes: require('./extractAttributes'), + extractUniforms: require('./extractUniforms'), + generateUniformAccessObject: require('./generateUniformAccessObject'), + setPrecision: require('./setPrecision'), + mapSize: require('./mapSize'), + mapType: require('./mapType') +}; \ No newline at end of file diff --git a/src/core/renderers/webgl/systems/shader/shader/mapSize.js b/src/core/renderers/webgl/systems/shader/shader/mapSize.js new file mode 100644 index 0000000..61d9b36 --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapSize.js @@ -0,0 +1,36 @@ +/** + * @class + * @memberof PIXI.glCore.shader + * @param type {String} + * @return {Number} + */ +var mapSize = function(type) +{ + return GLSL_TO_SIZE[type]; +}; + + +var GLSL_TO_SIZE = { + 'float': 1, + 'vec2': 2, + 'vec3': 3, + 'vec4': 4, + + 'int': 1, + 'ivec2': 2, + 'ivec3': 3, + 'ivec4': 4, + + 'bool': 1, + 'bvec2': 2, + 'bvec3': 3, + 'bvec4': 4, + + 'mat2': 4, + 'mat3': 9, + 'mat4': 16, + + 'sampler2D': 1 +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/mapType.js b/src/core/renderers/webgl/systems/shader/shader/mapType.js new file mode 100644 index 0000000..581dace --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/mapType.js @@ -0,0 +1,47 @@ + + +var mapSize = function(gl, type) +{ + if(!GL_TABLE) + { + var typeNames = Object.keys(GL_TO_GLSL_TYPES); + + GL_TABLE = {}; + + for(var i = 0; i < typeNames.length; ++i) + { + var tn = typeNames[i]; + GL_TABLE[ gl[tn] ] = GL_TO_GLSL_TYPES[tn]; + } + } + + return GL_TABLE[type]; +}; + +var GL_TABLE = null; + +var GL_TO_GLSL_TYPES = { + 'FLOAT': 'float', + 'FLOAT_VEC2': 'vec2', + 'FLOAT_VEC3': 'vec3', + 'FLOAT_VEC4': 'vec4', + + 'INT': 'int', + 'INT_VEC2': 'ivec2', + 'INT_VEC3': 'ivec3', + 'INT_VEC4': 'ivec4', + + 'BOOL': 'bool', + 'BOOL_VEC2': 'bvec2', + 'BOOL_VEC3': 'bvec3', + 'BOOL_VEC4': 'bvec4', + + 'FLOAT_MAT2': 'mat2', + 'FLOAT_MAT3': 'mat3', + 'FLOAT_MAT4': 'mat4', + + 'SAMPLER_2D': 'sampler2D', + 'SAMPLER_CUBE': 'samplerCube' +}; + +module.exports = mapSize; diff --git a/src/core/renderers/webgl/systems/shader/shader/setPrecision.js b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js new file mode 100644 index 0000000..383459a --- /dev/null +++ b/src/core/renderers/webgl/systems/shader/shader/setPrecision.js @@ -0,0 +1,18 @@ +/** + * Sets the float precision on the shader. If the precision is already present this function will do nothing + * @param {string} src the shader source + * @param {string} precision The float precision of the shader. Options are 'lowp', 'mediump' or 'highp'. + * + * @return {string} modified shader source + */ +var setPrecision = function(src, precision) +{ + if(src.substring(0, 9) !== 'precision') + { + return 'precision ' + precision + ' float;\n' + src; + } + + return src; +}; + +module.exports = setPrecision; diff --git a/src/core/renderers/webgl/systems/textures/GLTexture.js b/src/core/renderers/webgl/systems/textures/GLTexture.js new file mode 100644 index 0000000..cb8f6ad --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/GLTexture.js @@ -0,0 +1,333 @@ + +/** + * Helper class to create a WebGL Texture + * + * @class + * @memberof PIXI.glCore + * @param gl {WebGLRenderingContext} The current WebGL context + * @param width {number} the width of the texture + * @param height {number} the height of the texture + * @param format {number} the pixel format of the texture. defaults to gl.RGBA + * @param type {number} the gl type of the texture. defaults to gl.UNSIGNED_BYTE + */ +var Texture = function(gl, width, height, format, type) +{ + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + + /** + * The WebGL texture + * + * @member {WebGLTexture} + */ + this.texture = gl.createTexture(); + + /** + * If mipmapping was used for this texture, enable and disable with enableMipmap() + * + * @member {Boolean} + */ + // some settings.. + this.mipmap = false; + + + /** + * Set to true to enable pre-multiplied alpha + * + * @member {Boolean} + */ + this.premultiplyAlpha = false; + + /** + * The width of texture + * + * @member {Number} + */ + this.width = width || -1; + /** + * The height of texture + * + * @member {Number} + */ + this.height = height || -1; + + /** + * The pixel format of the texture. defaults to gl.RGBA + * + * @member {Number} + */ + this.format = format || gl.RGBA; + + /** + * The gl type of the texture. defaults to gl.UNSIGNED_BYTE + * + * @member {Number} + */ + this.type = type || gl.UNSIGNED_BYTE; + + +}; + +/** + * Uploads this texture to the GPU + * @param source {HTMLImageElement|ImageData|HTMLVideoElement} the source image of the texture + */ +Texture.prototype.upload = function(source) +{ + this.bind(); + + var gl = this.gl; + + + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + var newWidth = source.videoWidth || source.width; + var newHeight = source.videoHeight || source.height; + + if(newHeight !== this.height || newWidth !== this.width) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.format, this.type, source); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.format, this.type, source); + } + + // if the source is a video, we need to use the videoWidth / videoHeight properties as width / height will be incorrect. + this.width = newWidth; + this.height = newHeight; + +}; + +var FLOATING_POINT_AVAILABLE = false; + +/** + * Use a data source and uploads this texture to the GPU + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.prototype.uploadData = function(data, width, height) +{ + this.bind(); + + var gl = this.gl; + + + if(data instanceof Float32Array) + { + if(!FLOATING_POINT_AVAILABLE) + { + var ext = gl.getExtension("OES_texture_float"); + + if(ext) + { + FLOATING_POINT_AVAILABLE = true; + } + else + { + throw new Error('floating point textures not available'); + } + } + + this.type = gl.FLOAT; + } + else + { + // TODO support for other types + this.type = this.type || gl.UNSIGNED_BYTE; + } + + // what type of data? + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); + + + if(width !== this.width || height !== this.height) + { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, this.type, data || null); + } + else + { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, this.format, this.type, data || null); + } + + this.width = width; + this.height = height; + + +// texSubImage2D +}; + +/** + * Binds the texture + * @param location + */ +Texture.prototype.bind = function(location) +{ + var gl = this.gl; + + if(location !== undefined) + { + gl.activeTexture(gl.TEXTURE0 + location); + } + + gl.bindTexture(gl.TEXTURE_2D, this.texture); +}; + +/** + * Unbinds the texture + */ +Texture.prototype.unbind = function() +{ + var gl = this.gl; + gl.bindTexture(gl.TEXTURE_2D, null); +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.minFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + if(this.mipmap) + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, linear ? gl.LINEAR : gl.NEAREST); + } +}; + +/** + * @param linear {Boolean} if we want to use linear filtering or nearest neighbour interpolation + */ +Texture.prototype.magFilter = function( linear ) +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, linear ? gl.LINEAR : gl.NEAREST); +}; + +/** + * Enables mipmapping + */ +Texture.prototype.enableMipmap = function() +{ + var gl = this.gl; + + this.bind(); + + this.mipmap = true; + + gl.generateMipmap(gl.TEXTURE_2D); +}; + +/** + * Enables linear filtering + */ +Texture.prototype.enableLinearScaling = function() +{ + this.minFilter(true); + this.magFilter(true); +}; + +/** + * Enables nearest neighbour interpolation + */ +Texture.prototype.enableNearestScaling = function() +{ + this.minFilter(false); + this.magFilter(false); +}; + +/** + * Enables clamping on the texture so WebGL will not repeat it + */ +Texture.prototype.enableWrapClamp = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); +}; + +/** + * Enable tiling on the texture + */ +Texture.prototype.enableWrapRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); +}; + +Texture.prototype.enableWrapMirrorRepeat = function() +{ + var gl = this.gl; + + this.bind(); + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT); +}; + + +/** + * Destroys this texture + */ +Texture.prototype.destroy = function() +{ + var gl = this.gl; + //TODO + gl.deleteTexture(this.texture); +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param source {HTMLImageElement|ImageData} the source image of the texture + * @param premultiplyAlpha {Boolean} If we want to use pre-multiplied alpha + */ +Texture.fromSource = function(gl, source, premultiplyAlpha) +{ + var texture = new Texture(gl); + texture.premultiplyAlpha = premultiplyAlpha || false; + texture.upload(source); + + return texture; +}; + +/** + * @static + * @param gl {WebGLRenderingContext} The current WebGL context + * @param data {TypedArray} the data to upload to the texture + * @param width {number} the new width of the texture + * @param height {number} the new height of the texture + */ +Texture.fromData = function(gl, data, width, height) +{ + //console.log(data, width, height); + var texture = new Texture(gl); + texture.uploadData(data, width, height); + + return texture; +}; + + +module.exports = Texture; diff --git a/src/core/renderers/webgl/systems/textures/TextureGCSystem.js b/src/core/renderers/webgl/systems/textures/TextureGCSystem.js new file mode 100644 index 0000000..aff6b01 --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/TextureGCSystem.js @@ -0,0 +1,111 @@ +import WebGLSystem from '../WebGLSystem'; +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 TextureGCSystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System 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.texture; + 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.frameBuffer && 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.textureSystem; + + // 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/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js new file mode 100644 index 0000000..8416cec --- /dev/null +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -0,0 +1,283 @@ +import WebGLSystem from '../WebGLSystem'; +import GLTexture from './GLTexture'; +import { removeItems } from '../../../../utils'; + +/** + * @class + * @extends PIXI.WebGLSystem + * @memberof PIXI + */ +export default class TextureSystem extends WebGLSystem +{ + /** + * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for. + */ + constructor(renderer) + { + super(renderer); + + // TODO set to max textures... + this.boundTextures = [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ]; + + this.currentLocation = -1; + + this.managedTextures = []; + + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + contextChange() + { + const gl = this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // TODO move this.. to a nice make empty textures class.. + this.emptyTextures = {} + + this.emptyTextures[gl.TEXTURE_2D] = new GLTexture.fromData(this.gl, null, 1, 1); + this.emptyTextures[gl.TEXTURE_CUBE_MAP] = new GLTexture(this.gl); + + gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.emptyTextures[gl.TEXTURE_CUBE_MAP].texture); + + for (var i = 0; i < 6; i++) + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + + 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) + { + + const gl = this.gl; + + + location = location || 0; + + if(this.currentLocation !== location) + { + this.currentLocation = location; + gl.activeTexture(gl.TEXTURE0 + location); + } + + if(texture) + { + texture = texture.baseTexture || texture; + + if(texture.valid) + { + + 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(gl.TEXTURE_2D, this.emptyTextures[gl.TEXTURE_2D].texture); + this.boundTextures[location] = null; + } + } + + unbind(texture) + { + const gl = this.gl; + + for (var i = 0; i < this.boundTextures.length; i++) { + + if(this.boundTextures[i] === texture) + { + if(this.currentLocation !== i) + { + gl.activeTexture(gl.TEXTURE0 + i); + this.currentLocation = i; + } + + gl.bindTexture(gl.TEXTURE_2D, this.emptyTextures[texture.target].texture); + this.boundTextures[i] = null; + } + } + } + + initTexture(texture) + { + const gl = this.gl; + + var glTexture = new GLTexture(this.gl, -1, -1, texture.format, texture.type); + glTexture.premultiplyAlpha = texture.premultiplyAlpha; + // guarentee an update.. + glTexture.dirtyId = -1; + + texture._glTextures[this.CONTEXT_UID] = glTexture; + + this.managedTextures.push(texture); + texture.on('dispose', this.destroyTexture, this); + + return glTexture; + } + + updateTexture(texture) + { + const glTexture = texture._glTextures[this.CONTEXT_UID]; + const gl = this.gl; + + // TODO there are only 3 textures as far as im aware? + // Cube / 2D and later 3d. (the latter is WebGL2, we will get to that soon!) + if(texture.target === gl.TEXTURE_CUBE_MAP) + { + // console.log( gl.UNSIGNED_BYTE) + for (var i = 0; i < texture.sides.length; i++) + { + // TODO - we should only upload what changed.. + // but im sure this is not going to be a problem just yet! + var texturePart = texture.sides[i]; + + if(texturePart.resource) + { + if(texturePart.resource.uploadable) + { + + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.format, + texture.type, + texturePart.resource.source); + } + else + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.width, + texture.height, + 0, + texture.format, + texture.type, + texturePart.resource.source); + } + } + else + { + gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + texturePart.side, + 0, + texture.format, + texture.width, + texture.height, + 0, + texture.format, + texture.type, + null); + } + } + } + else + { + if(texture.resource) + { + if(texture.resource.uploadable) + { + glTexture.upload(texture.resource.source); + + } + else + { + glTexture.uploadData(texture.resource.source, texture.width, texture.height); + } + } + else + { + glTexture.uploadData(null, texture.width, texture.height); + } + } + + // lets only update what changes.. + this.setStyle(texture); + } + + /** + * Deletes the texture from WebGL + * + * @param {PIXI.BaseTexture|PIXI.Texture} texture - the texture to destroy + * @param {boolean} [skipRemove=false] - Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) + { + texture = texture.baseTexture || texture; + + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + this.unbind(texture); + + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + const i = this.managedTextures.indexOf(texture); + + if (i !== -1) + { + removeItems(this.managedTextures, i, 1); + } + } + } + } + + setStyle(texture) + { + const gl = this.gl; + + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_S, texture.wrapMode); + gl.texParameteri(texture.target, gl.TEXTURE_WRAP_T, texture.wrapMode); + + if(texture.mipmap) + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); + } + else + { + gl.texParameteri(texture.target, gl.TEXTURE_MIN_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } + + gl.texParameteri(texture.target, gl.TEXTURE_MAG_FILTER, texture.scaleMode ? gl.LINEAR : gl.NEAREST); + } +} \ No newline at end of file diff --git a/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js b/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js index 8326e63..3544cb9 100644 --- a/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js +++ b/src/core/renderers/webgl/utils/checkMaxIfStatmentsInShader.js @@ -11,8 +11,6 @@ export default function checkMaxIfStatmentsInShader(maxIfs, gl) { - const createTempContext = !gl; - // @if DEBUG if (maxIfs === 0) { @@ -20,16 +18,6 @@ } // @endif - if (createTempContext) - { - const tinyCanvas = document.createElement('canvas'); - - tinyCanvas.width = 1; - tinyCanvas.height = 1; - - gl = glCore.createContext(tinyCanvas); - } - const shader = gl.createShader(gl.FRAGMENT_SHADER); while (true) // eslint-disable-line no-constant-condition @@ -50,15 +38,6 @@ } } - if (createTempContext) - { - // get rid of context - if (gl.getExtension('WEBGL_lose_context')) - { - gl.getExtension('WEBGL_lose_context').loseContext(); - } - } - return maxIfs; } diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index 5fe92ad..c0b538b 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -2,6 +2,7 @@ import generateUniformsSync from './generateUniformsSync'; import glCore from 'pixi-gl-core'; import { ProgramCache } from '../utils'; +import getTestContext from '../utils/getTestContext'; let UID = 0; @@ -53,7 +54,7 @@ */ extractData(vertexSrc, fragmentSrc) { - const gl = glCore._testingContext || Program.getTestingContext(); + const gl = getTestContext(); if (!gl) { @@ -202,36 +203,6 @@ } /** - * returns a little webGL context to use for program inspection. - * - * @static - * @private - * @returns {webGL-context} a gl context to test with - */ - static getTestingContext() - { - try - { - if (!Program.testingContext) - { - const canvas = document.createElement('canvas'); - - canvas.width = 1; - canvas.height = 1; - - Program.testingContext = glCore.createContext(canvas); - } - } - - catch (e) - { - // eslint-disable-line no-empty - } - - return Program.testingContext; - } - - /** * A short hand function to create a program based of a vertex and fragment shader * this method will also check to see if there is a cached program. * diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 22df803..1341ec3 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -1,11 +1,11 @@ import ObjectRenderer from '../../renderers/webgl/utils/ObjectRenderer'; import WebGLRenderer from '../../renderers/webgl/WebGLRenderer'; +import GLBuffer from '../../renderers/webgl/systems/geometry/GLBuffer'; import createIndicesForQuads from '../../utils/createIndicesForQuads'; import generateMultiTextureShader from './generateMultiTextureShader'; import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; -import glCore from 'pixi-gl-core'; import bitTwiddle from 'bit-twiddle'; let TICK = 0; @@ -115,7 +115,6 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } - glCore._testingContext = gl; const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); const sampleValues = new Int32Array(this.MAX_TEXTURES); @@ -128,7 +127,7 @@ shader.uniformGroup.add('default', {uSamplers:sampleValues}, true);//this.renderer.globalUniforms; shader.uniforms.globals = this.renderer.globalUniforms; - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.indexBuffer = GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. @@ -137,7 +136,7 @@ for (let i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); /* eslint-disable max-len */ var attributeData = shader.program.attributeData; @@ -389,7 +388,7 @@ if (this.vaoMax <= this.vertexCount) { this.vaoMax++; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); /* eslint-disable max-len */ diff --git a/src/core/utils/getTestContext.js b/src/core/utils/getTestContext.js new file mode 100644 index 0000000..40ffe25 --- /dev/null +++ b/src/core/utils/getTestContext.js @@ -0,0 +1,36 @@ + + +let context = null; + + +/** + * returns a little webGL context to use for program inspection. + * + * @static + * @private + * @returns {webGL-context} a gl context to test with + */ + +export default function getTestContext() +{ + if(!context) + { + const canvas = document.createElement('canvas'); + const options = {}; + + canvas.width = 1; + canvas.height = 1; + + + context = canvas.getContext('webgl', options) || + canvas.getContext('experimental-webgl', options); + + if (!context) + { + // fail, not able to get a context + throw new Error('This browser does not support webGL. Try using the canvas renderer'); + } + } + + return context; +} diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index f741797..78d24c3 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -1,5 +1,4 @@ import * as core from '../../core'; -import glCore from 'pixi-gl-core'; const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; diff --git a/src/particles/webgl/ParticleBuffer.js b/src/particles/webgl/ParticleBuffer.js index 05e8f16..58b2642 100644 --- a/src/particles/webgl/ParticleBuffer.js +++ b/src/particles/webgl/ParticleBuffer.js @@ -1,4 +1,6 @@ -import glCore from 'pixi-gl-core'; +import GLBuffer from '../../core/renderers/webgl/systems/geometry/GLBuffer'; +import VertexArrayObject from '../../core/renderers/webgl/systems/geometry/VertexArrayObject'; + import createIndicesForQuads from '../../core/utils/createIndicesForQuads'; /** @@ -122,7 +124,7 @@ * @member {Uint16Array} */ this.indices = createIndicesForQuads(this.size); - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.indexBuffer = GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); this.dynamicStride = 0; @@ -136,7 +138,7 @@ } this.dynamicData = new Float32Array(this.size * this.dynamicStride * 4); - this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); + this.dynamicBuffer = GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); // static // let staticOffset = 0; @@ -153,9 +155,9 @@ } this.staticData = new Float32Array(this.size * this.staticStride * 4); - this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); + this.staticBuffer = GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); - this.vao = new glCore.VertexArrayObject(gl) + this.vao = new VertexArrayObject(gl) .addIndex(this.indexBuffer); for (let i = 0; i < this.dynamicProperties.length; ++i)