diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index ac37001..c5d09cd 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -142,6 +142,8 @@ { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); + + const isArray = uniformData.name.match(/\[.*?\]/, ''); const type = shaderUtils.mapType(gl, uniformData.type); // if (!name.match(maskRegex)) @@ -150,6 +152,7 @@ uniforms[name] = { type: type, size: uniformData.size, + isArray:isArray, value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index ac37001..c5d09cd 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -142,6 +142,8 @@ { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); + + const isArray = uniformData.name.match(/\[.*?\]/, ''); const type = shaderUtils.mapType(gl, uniformData.type); // if (!name.match(maskRegex)) @@ -150,6 +152,7 @@ uniforms[name] = { type: type, size: uniformData.size, + isArray:isArray, value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index ad4fcfc..2f8dc56 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -40,54 +40,41 @@ // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniformGroup.uniforms[i]; - - if (!uniform) // bad as we need check wher ethe uniforms are.. + if (this.uniformGroup.uniforms[i] instanceof Array) { - //this.uniformGroup.uniforms[i] = program.uniformData[i].value; - } - else if (uniform instanceof Array) - { - this.uniformGroup.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(this.uniformGroup.uniforms[i]); } } } + checkUniformExists(name, group) + { + if(group.uniforms[name]) + { + return true; + } + + for (const i in group.uniforms) + { + const uniform = group.uniforms[i]; + + if(uniform.group) + { + if( this.checkUniformExists(name, uniform) ) + { + return true; + } + } + } + + return false; + } + get uniforms() { return this.uniformGroup.uniforms; } -/* -var offsetBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW); - -var colorBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); - -var instanceCount = 4; -var ext = gl.getExtension("ANGLE_instanced_arrays"); // Vendor prefixes may apply! - -// Bind the rest of the vertex attributes normally -bindMeshArrays(gl); - -// Bind the instance position data -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.enableVertexAttribArray(offsetLocation); -gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 12, 0); -ext.vertexAttribDivisorANGLE(offsetLocation, 1); // This makes it instanced! - -// Bind the instance color data -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.enableVertexAttribArray(colorLocation); -gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 16, 0); -ext.vertexAttribDivisorANGLE(colorLocation, 1); // This makes it instanced! - -// Draw the instanced meshes -ext.drawElementsInstancedANGLE(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount); -*/ /** * A short hand function to create a shader based of a vertex and fragment shader * diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index ac37001..c5d09cd 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -142,6 +142,8 @@ { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); + + const isArray = uniformData.name.match(/\[.*?\]/, ''); const type = shaderUtils.mapType(gl, uniformData.type); // if (!name.match(maskRegex)) @@ -150,6 +152,7 @@ uniforms[name] = { type: type, size: uniformData.size, + isArray:isArray, value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index ad4fcfc..2f8dc56 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -40,54 +40,41 @@ // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniformGroup.uniforms[i]; - - if (!uniform) // bad as we need check wher ethe uniforms are.. + if (this.uniformGroup.uniforms[i] instanceof Array) { - //this.uniformGroup.uniforms[i] = program.uniformData[i].value; - } - else if (uniform instanceof Array) - { - this.uniformGroup.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(this.uniformGroup.uniforms[i]); } } } + checkUniformExists(name, group) + { + if(group.uniforms[name]) + { + return true; + } + + for (const i in group.uniforms) + { + const uniform = group.uniforms[i]; + + if(uniform.group) + { + if( this.checkUniformExists(name, uniform) ) + { + return true; + } + } + } + + return false; + } + get uniforms() { return this.uniformGroup.uniforms; } -/* -var offsetBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW); - -var colorBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); - -var instanceCount = 4; -var ext = gl.getExtension("ANGLE_instanced_arrays"); // Vendor prefixes may apply! - -// Bind the rest of the vertex attributes normally -bindMeshArrays(gl); - -// Bind the instance position data -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.enableVertexAttribArray(offsetLocation); -gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 12, 0); -ext.vertexAttribDivisorANGLE(offsetLocation, 1); // This makes it instanced! - -// Bind the instance color data -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.enableVertexAttribArray(colorLocation); -gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 16, 0); -ext.vertexAttribDivisorANGLE(colorLocation, 1); // This makes it instanced! - -// Draw the instanced meshes -ext.drawElementsInstancedANGLE(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount); -*/ /** * A short hand function to create a shader based of a vertex and fragment shader * diff --git a/src/core/shader/UniformGroup.js b/src/core/shader/UniformGroup.js index 8768226..e2ed22a 100644 --- a/src/core/shader/UniformGroup.js +++ b/src/core/shader/UniformGroup.js @@ -1,5 +1,3 @@ -import Program from './Program'; -import generateUniformsSync from './generateUniformsSync'; let UID = 0; diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index ac37001..c5d09cd 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -142,6 +142,8 @@ { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); + + const isArray = uniformData.name.match(/\[.*?\]/, ''); const type = shaderUtils.mapType(gl, uniformData.type); // if (!name.match(maskRegex)) @@ -150,6 +152,7 @@ uniforms[name] = { type: type, size: uniformData.size, + isArray:isArray, value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index ad4fcfc..2f8dc56 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -40,54 +40,41 @@ // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniformGroup.uniforms[i]; - - if (!uniform) // bad as we need check wher ethe uniforms are.. + if (this.uniformGroup.uniforms[i] instanceof Array) { - //this.uniformGroup.uniforms[i] = program.uniformData[i].value; - } - else if (uniform instanceof Array) - { - this.uniformGroup.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(this.uniformGroup.uniforms[i]); } } } + checkUniformExists(name, group) + { + if(group.uniforms[name]) + { + return true; + } + + for (const i in group.uniforms) + { + const uniform = group.uniforms[i]; + + if(uniform.group) + { + if( this.checkUniformExists(name, uniform) ) + { + return true; + } + } + } + + return false; + } + get uniforms() { return this.uniformGroup.uniforms; } -/* -var offsetBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW); - -var colorBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); - -var instanceCount = 4; -var ext = gl.getExtension("ANGLE_instanced_arrays"); // Vendor prefixes may apply! - -// Bind the rest of the vertex attributes normally -bindMeshArrays(gl); - -// Bind the instance position data -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.enableVertexAttribArray(offsetLocation); -gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 12, 0); -ext.vertexAttribDivisorANGLE(offsetLocation, 1); // This makes it instanced! - -// Bind the instance color data -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.enableVertexAttribArray(colorLocation); -gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 16, 0); -ext.vertexAttribDivisorANGLE(colorLocation, 1); // This makes it instanced! - -// Draw the instanced meshes -ext.drawElementsInstancedANGLE(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount); -*/ /** * A short hand function to create a shader based of a vertex and fragment shader * diff --git a/src/core/shader/UniformGroup.js b/src/core/shader/UniformGroup.js index 8768226..e2ed22a 100644 --- a/src/core/shader/UniformGroup.js +++ b/src/core/shader/UniformGroup.js @@ -1,5 +1,3 @@ -import Program from './Program'; -import generateUniformsSync from './generateUniformsSync'; let UID = 0; diff --git a/src/core/shader/generateUniformsSync.js b/src/core/shader/generateUniformsSync.js index b7241ef..8a7cf55 100644 --- a/src/core/shader/generateUniformsSync.js +++ b/src/core/shader/generateUniformsSync.js @@ -1,153 +1,190 @@ + + +//cv = CachedValue +//v = value +//ud = uniformData +//uv = uniformValue +//l = loaction const GLSL_TO_SINGLE_SETTERS_CACHED = { - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, + float:` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; + vec2:` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', + vec3:` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + gl.uniform3f(location, v[0], v[1], v[2]) + }`, - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)' + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', }; const GLSL_TO_ARRAY_SETTERS = { - float: `gl.uniform1fv(location, value)`, + float: `gl.uniform1fv(location, v)`, - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)' + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', }; -export default function generateUniformsSync(uniformData) + +export default function generateUniformsSync(group, uniformData) { - let textureCount = 1; - let func = `var value = null; - var cacheValue = null + let textureCount = 0; + let func = `var v = null; + var cv = null var gl = renderer.gl`; - for (const i in uniformData) + for (const i in group.uniforms) { const data = uniformData[i]; - // console.log(i, data.size); + + if(!data) + { + if(group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + ` + } + + continue; + } // TODO && uniformData[i].value !== 0 <-- do we still need this? if (data.type === 'float' && data.size === 1) { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; } - else if (data.type === 'sampler2D' && data.size === 1) + else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) { - func += `\nif (uniformValues.${i}.baseTexture) -{ - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount++}, false); + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - } -} -else -{ - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n` - uniformValues.${i}.bind(); -}`; + textureCount++; } else if (data.type === 'mat3' && data.size === 1) { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; + if(group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } } else if (data.type === 'vec2' && data.size === 1) { // TODO - do we need both here? // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; + if(group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + }\n`; + } } else { const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); + const template = templateType[data.type].replace('location', `ud.${i}.location`); - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; } } - //console.log(' --------------- ') - // console.log(func); + // console.log(' --------------- ') + // console.log(func); - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func } diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index ac37001..c5d09cd 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -142,6 +142,8 @@ { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); + + const isArray = uniformData.name.match(/\[.*?\]/, ''); const type = shaderUtils.mapType(gl, uniformData.type); // if (!name.match(maskRegex)) @@ -150,6 +152,7 @@ uniforms[name] = { type: type, size: uniformData.size, + isArray:isArray, value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index ad4fcfc..2f8dc56 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -40,54 +40,41 @@ // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniformGroup.uniforms[i]; - - if (!uniform) // bad as we need check wher ethe uniforms are.. + if (this.uniformGroup.uniforms[i] instanceof Array) { - //this.uniformGroup.uniforms[i] = program.uniformData[i].value; - } - else if (uniform instanceof Array) - { - this.uniformGroup.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(this.uniformGroup.uniforms[i]); } } } + checkUniformExists(name, group) + { + if(group.uniforms[name]) + { + return true; + } + + for (const i in group.uniforms) + { + const uniform = group.uniforms[i]; + + if(uniform.group) + { + if( this.checkUniformExists(name, uniform) ) + { + return true; + } + } + } + + return false; + } + get uniforms() { return this.uniformGroup.uniforms; } -/* -var offsetBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW); - -var colorBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); - -var instanceCount = 4; -var ext = gl.getExtension("ANGLE_instanced_arrays"); // Vendor prefixes may apply! - -// Bind the rest of the vertex attributes normally -bindMeshArrays(gl); - -// Bind the instance position data -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.enableVertexAttribArray(offsetLocation); -gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 12, 0); -ext.vertexAttribDivisorANGLE(offsetLocation, 1); // This makes it instanced! - -// Bind the instance color data -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.enableVertexAttribArray(colorLocation); -gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 16, 0); -ext.vertexAttribDivisorANGLE(colorLocation, 1); // This makes it instanced! - -// Draw the instanced meshes -ext.drawElementsInstancedANGLE(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount); -*/ /** * A short hand function to create a shader based of a vertex and fragment shader * diff --git a/src/core/shader/UniformGroup.js b/src/core/shader/UniformGroup.js index 8768226..e2ed22a 100644 --- a/src/core/shader/UniformGroup.js +++ b/src/core/shader/UniformGroup.js @@ -1,5 +1,3 @@ -import Program from './Program'; -import generateUniformsSync from './generateUniformsSync'; let UID = 0; diff --git a/src/core/shader/generateUniformsSync.js b/src/core/shader/generateUniformsSync.js index b7241ef..8a7cf55 100644 --- a/src/core/shader/generateUniformsSync.js +++ b/src/core/shader/generateUniformsSync.js @@ -1,153 +1,190 @@ + + +//cv = CachedValue +//v = value +//ud = uniformData +//uv = uniformValue +//l = loaction const GLSL_TO_SINGLE_SETTERS_CACHED = { - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, + float:` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; + vec2:` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', + vec3:` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + gl.uniform3f(location, v[0], v[1], v[2]) + }`, - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)' + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', }; const GLSL_TO_ARRAY_SETTERS = { - float: `gl.uniform1fv(location, value)`, + float: `gl.uniform1fv(location, v)`, - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)' + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', }; -export default function generateUniformsSync(uniformData) + +export default function generateUniformsSync(group, uniformData) { - let textureCount = 1; - let func = `var value = null; - var cacheValue = null + let textureCount = 0; + let func = `var v = null; + var cv = null var gl = renderer.gl`; - for (const i in uniformData) + for (const i in group.uniforms) { const data = uniformData[i]; - // console.log(i, data.size); + + if(!data) + { + if(group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + ` + } + + continue; + } // TODO && uniformData[i].value !== 0 <-- do we still need this? if (data.type === 'float' && data.size === 1) { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; } - else if (data.type === 'sampler2D' && data.size === 1) + else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) { - func += `\nif (uniformValues.${i}.baseTexture) -{ - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount++}, false); + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - } -} -else -{ - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n` - uniformValues.${i}.bind(); -}`; + textureCount++; } else if (data.type === 'mat3' && data.size === 1) { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; + if(group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } } else if (data.type === 'vec2' && data.size === 1) { // TODO - do we need both here? // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; + if(group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + }\n`; + } } else { const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); + const template = templateType[data.type].replace('location', `ud.${i}.location`); - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; } } - //console.log(' --------------- ') - // console.log(func); + // console.log(' --------------- ') + // console.log(func); - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func } diff --git a/src/core/shader/generateUniformsSync2.js b/src/core/shader/generateUniformsSync2.js deleted file mode 100644 index 8a0fd6b..0000000 --- a/src/core/shader/generateUniformsSync2.js +++ /dev/null @@ -1,190 +0,0 @@ -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, - - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; - - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', - - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', - - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)', - sampler2DArray: 'gl.uniform1i(location, value)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, value)`, - - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', - - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', - - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', - - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)', - sampler2DArray: 'gl.uniform1iv(location, value)', -}; - -export default function generateUniformsSync2(group, uniformData) -{ - let textureCount = 0; - let func = `var value = null; - var cacheValue = null - var gl = renderer.gl`; - - - for (const i in group.uniforms) - { - const data = uniformData[i]; - //group.uniforms[i]; - // console.log(i, data); - if(!data) - { - if(group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uniformValues.${i}); - ` - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; - } - else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1) - { - func += `\nif (uniformValues.${i}.baseTexture) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } - - /* - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount}, false); - - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - }*/ -} -else if(uniformValues.${i}._new) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } -} -else -{ - // console.log("binding..") - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount}); - - uniformValues.${i}.bind(); -}`; - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; - -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); - - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; - } - } - - console.log(' --------------- ') - console.log(func); - - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index ac37001..c5d09cd 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -142,6 +142,8 @@ { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); + + const isArray = uniformData.name.match(/\[.*?\]/, ''); const type = shaderUtils.mapType(gl, uniformData.type); // if (!name.match(maskRegex)) @@ -150,6 +152,7 @@ uniforms[name] = { type: type, size: uniformData.size, + isArray:isArray, value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index ad4fcfc..2f8dc56 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -40,54 +40,41 @@ // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniformGroup.uniforms[i]; - - if (!uniform) // bad as we need check wher ethe uniforms are.. + if (this.uniformGroup.uniforms[i] instanceof Array) { - //this.uniformGroup.uniforms[i] = program.uniformData[i].value; - } - else if (uniform instanceof Array) - { - this.uniformGroup.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(this.uniformGroup.uniforms[i]); } } } + checkUniformExists(name, group) + { + if(group.uniforms[name]) + { + return true; + } + + for (const i in group.uniforms) + { + const uniform = group.uniforms[i]; + + if(uniform.group) + { + if( this.checkUniformExists(name, uniform) ) + { + return true; + } + } + } + + return false; + } + get uniforms() { return this.uniformGroup.uniforms; } -/* -var offsetBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW); - -var colorBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); - -var instanceCount = 4; -var ext = gl.getExtension("ANGLE_instanced_arrays"); // Vendor prefixes may apply! - -// Bind the rest of the vertex attributes normally -bindMeshArrays(gl); - -// Bind the instance position data -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.enableVertexAttribArray(offsetLocation); -gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 12, 0); -ext.vertexAttribDivisorANGLE(offsetLocation, 1); // This makes it instanced! - -// Bind the instance color data -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.enableVertexAttribArray(colorLocation); -gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 16, 0); -ext.vertexAttribDivisorANGLE(colorLocation, 1); // This makes it instanced! - -// Draw the instanced meshes -ext.drawElementsInstancedANGLE(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount); -*/ /** * A short hand function to create a shader based of a vertex and fragment shader * diff --git a/src/core/shader/UniformGroup.js b/src/core/shader/UniformGroup.js index 8768226..e2ed22a 100644 --- a/src/core/shader/UniformGroup.js +++ b/src/core/shader/UniformGroup.js @@ -1,5 +1,3 @@ -import Program from './Program'; -import generateUniformsSync from './generateUniformsSync'; let UID = 0; diff --git a/src/core/shader/generateUniformsSync.js b/src/core/shader/generateUniformsSync.js index b7241ef..8a7cf55 100644 --- a/src/core/shader/generateUniformsSync.js +++ b/src/core/shader/generateUniformsSync.js @@ -1,153 +1,190 @@ + + +//cv = CachedValue +//v = value +//ud = uniformData +//uv = uniformValue +//l = loaction const GLSL_TO_SINGLE_SETTERS_CACHED = { - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, + float:` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; + vec2:` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', + vec3:` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + gl.uniform3f(location, v[0], v[1], v[2]) + }`, - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)' + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', }; const GLSL_TO_ARRAY_SETTERS = { - float: `gl.uniform1fv(location, value)`, + float: `gl.uniform1fv(location, v)`, - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)' + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', }; -export default function generateUniformsSync(uniformData) + +export default function generateUniformsSync(group, uniformData) { - let textureCount = 1; - let func = `var value = null; - var cacheValue = null + let textureCount = 0; + let func = `var v = null; + var cv = null var gl = renderer.gl`; - for (const i in uniformData) + for (const i in group.uniforms) { const data = uniformData[i]; - // console.log(i, data.size); + + if(!data) + { + if(group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + ` + } + + continue; + } // TODO && uniformData[i].value !== 0 <-- do we still need this? if (data.type === 'float' && data.size === 1) { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; } - else if (data.type === 'sampler2D' && data.size === 1) + else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) { - func += `\nif (uniformValues.${i}.baseTexture) -{ - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount++}, false); + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - } -} -else -{ - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n` - uniformValues.${i}.bind(); -}`; + textureCount++; } else if (data.type === 'mat3' && data.size === 1) { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; + if(group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } } else if (data.type === 'vec2' && data.size === 1) { // TODO - do we need both here? // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; + if(group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + }\n`; + } } else { const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); + const template = templateType[data.type].replace('location', `ud.${i}.location`); - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; } } - //console.log(' --------------- ') - // console.log(func); + // console.log(' --------------- ') + // console.log(func); - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func } diff --git a/src/core/shader/generateUniformsSync2.js b/src/core/shader/generateUniformsSync2.js deleted file mode 100644 index 8a0fd6b..0000000 --- a/src/core/shader/generateUniformsSync2.js +++ /dev/null @@ -1,190 +0,0 @@ -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, - - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; - - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', - - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', - - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)', - sampler2DArray: 'gl.uniform1i(location, value)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, value)`, - - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', - - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', - - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', - - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)', - sampler2DArray: 'gl.uniform1iv(location, value)', -}; - -export default function generateUniformsSync2(group, uniformData) -{ - let textureCount = 0; - let func = `var value = null; - var cacheValue = null - var gl = renderer.gl`; - - - for (const i in group.uniforms) - { - const data = uniformData[i]; - //group.uniforms[i]; - // console.log(i, data); - if(!data) - { - if(group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uniformValues.${i}); - ` - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; - } - else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1) - { - func += `\nif (uniformValues.${i}.baseTexture) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } - - /* - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount}, false); - - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - }*/ -} -else if(uniformValues.${i}._new) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } -} -else -{ - // console.log("binding..") - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount}); - - uniformValues.${i}.bind(); -}`; - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; - -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); - - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; - } - } - - console.log(' --------------- ') - console.log(func); - - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 5b486ba..57b4b3c 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -7,6 +7,10 @@ import Buffer from './BatchBuffer'; import settings from '../../settings'; import bitTwiddle from 'bit-twiddle'; +import Geometry from '../../geometry/Geometry'; +//TODO rename this +import Buffer_GEOM from '../../geometry/Buffer'; + let TICK = 0; let TEXTURE_TICK = 0; @@ -65,6 +69,7 @@ * @member {Uint16Array} */ this.indices = createIndicesForQuads(this.size); + this.indexBuffer = new Buffer_GEOM(this.indices, true, true); /** * The default shaders that is used if a sprite doesn't have a more specific one. @@ -115,50 +120,27 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } - const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); - const sampleValues = new Int32Array(this.MAX_TEXTURES); - - for (let i = 0; i < this.MAX_TEXTURES; i++) - { - sampleValues[i] = i; - } - - shader.uniformGroup.add('default', {uSamplers:sampleValues}, true);//this.renderer.globalUniforms; - shader.uniforms.globals = this.renderer.globalUniforms; - - this.indexBuffer = GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // generate generateMultiTextureProgram, may be a better move? + this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. - - this.renderer.geometry.bindVao(null); - for (let i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + var buffer = new Buffer_GEOM(null, false); + /* eslint-disable max-len */ - - var attributeData = shader.program.attributeData; - // build the vao object that will render.. - this.vaos[i] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[i].addAttribute(this.vertexBuffers[i], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[i] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[i] = buffer; } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; - - this.boundTextures = new Array(this.MAX_TEXTURES); } /** @@ -221,9 +203,7 @@ const float32View = buffer.float32View; const uint32View = buffer.uint32View; - const boundTextures = this.boundTextures; - const rendererBoundTextures = this.renderer.boundTextures; - const touch = 0//this.renderer.textureGC.count; + const touch = 0;//this.renderer.textureGC.count; let index = 0; let nextTexture; @@ -244,13 +224,6 @@ let i; - // copy textures.. - for (i = 0; i < MAX_TEXTURES; ++i) - { - boundTextures[i] = rendererBoundTextures[i]; - boundTextures[i]._virtalBoundId = i; - } - for (i = 0; i < this.currentIndex; ++i) { // upload the sprite elemetns... @@ -371,37 +344,32 @@ if (this.vaoMax <= this.vertexCount) { this.vaoMax++; - this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + + let buffer = new Buffer_GEOM(null, false); /* eslint-disable max-len */ - - var attributeData = this.shader.program.attributeData; - - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[this.vertexCount].addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[this.vertexCount] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[this.vertexCount] = buffer; } - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); this.vertexCount++; } else { // lets use the faster option, always use buffer number 0 - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, true); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + + this.renderer.geometry.updateBuffers(); } /// render the groups.. @@ -409,15 +377,6 @@ var group = groups[i]; var groupTextureCount = group.textureCount; - //shader = this.shaders[groupTextureCount-1]; - - //if(!shader) - // { - // shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - //} - - /// this.renderer.shader.bind(shader); for (var j = 0; j < groupTextureCount; j++) { @@ -425,38 +384,11 @@ } // set the blend mode.. - //this.renderer.state.setBlendMode( group.blend ); + this.renderer.state.setBlendMode( group.blend ); gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); } -/* - // render the groups.. - for (i = 0; i < groupCount; ++i) - { - const group = groups[i]; - const groupTextureCount = group.textureCount; - for (let j = 0; j < groupTextureCount; j++) - { - currentTexture = group.textures[j]; - - // reset virtual ids.. - // lets do a quick check.. - if (rendererBoundTextures[group.ids[j]] !== currentTexture) - { - this.renderer.texture.bind(currentTexture, group.ids[j], true); - } - - // reset the virtualId.. - currentTexture._virtalBoundId = -1; - } - - // set the blend mode.. - this.renderer.state.setBlendMode(group.blend); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } -*/ // reset elements for the next flush this.currentIndex = 0; } @@ -466,16 +398,12 @@ */ start() { - // this.renderer._bindGLShader(this.shader); - this.renderer.shader.bind(this.shader, true); - this.renderer.shader.syncUniformGroup(this.shader.uniformGroup); + this.renderer.shader.bind(this.shader); if (settings.CAN_UPLOAD_SAME_BUFFER) { // bind buffer #0, we don't need others - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].bind(); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); } } diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index ac37001..c5d09cd 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -142,6 +142,8 @@ { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); + + const isArray = uniformData.name.match(/\[.*?\]/, ''); const type = shaderUtils.mapType(gl, uniformData.type); // if (!name.match(maskRegex)) @@ -150,6 +152,7 @@ uniforms[name] = { type: type, size: uniformData.size, + isArray:isArray, value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index ad4fcfc..2f8dc56 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -40,54 +40,41 @@ // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniformGroup.uniforms[i]; - - if (!uniform) // bad as we need check wher ethe uniforms are.. + if (this.uniformGroup.uniforms[i] instanceof Array) { - //this.uniformGroup.uniforms[i] = program.uniformData[i].value; - } - else if (uniform instanceof Array) - { - this.uniformGroup.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(this.uniformGroup.uniforms[i]); } } } + checkUniformExists(name, group) + { + if(group.uniforms[name]) + { + return true; + } + + for (const i in group.uniforms) + { + const uniform = group.uniforms[i]; + + if(uniform.group) + { + if( this.checkUniformExists(name, uniform) ) + { + return true; + } + } + } + + return false; + } + get uniforms() { return this.uniformGroup.uniforms; } -/* -var offsetBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW); - -var colorBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); - -var instanceCount = 4; -var ext = gl.getExtension("ANGLE_instanced_arrays"); // Vendor prefixes may apply! - -// Bind the rest of the vertex attributes normally -bindMeshArrays(gl); - -// Bind the instance position data -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.enableVertexAttribArray(offsetLocation); -gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 12, 0); -ext.vertexAttribDivisorANGLE(offsetLocation, 1); // This makes it instanced! - -// Bind the instance color data -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.enableVertexAttribArray(colorLocation); -gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 16, 0); -ext.vertexAttribDivisorANGLE(colorLocation, 1); // This makes it instanced! - -// Draw the instanced meshes -ext.drawElementsInstancedANGLE(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount); -*/ /** * A short hand function to create a shader based of a vertex and fragment shader * diff --git a/src/core/shader/UniformGroup.js b/src/core/shader/UniformGroup.js index 8768226..e2ed22a 100644 --- a/src/core/shader/UniformGroup.js +++ b/src/core/shader/UniformGroup.js @@ -1,5 +1,3 @@ -import Program from './Program'; -import generateUniformsSync from './generateUniformsSync'; let UID = 0; diff --git a/src/core/shader/generateUniformsSync.js b/src/core/shader/generateUniformsSync.js index b7241ef..8a7cf55 100644 --- a/src/core/shader/generateUniformsSync.js +++ b/src/core/shader/generateUniformsSync.js @@ -1,153 +1,190 @@ + + +//cv = CachedValue +//v = value +//ud = uniformData +//uv = uniformValue +//l = loaction const GLSL_TO_SINGLE_SETTERS_CACHED = { - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, + float:` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; + vec2:` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', + vec3:` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + gl.uniform3f(location, v[0], v[1], v[2]) + }`, - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)' + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', }; const GLSL_TO_ARRAY_SETTERS = { - float: `gl.uniform1fv(location, value)`, + float: `gl.uniform1fv(location, v)`, - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)' + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', }; -export default function generateUniformsSync(uniformData) + +export default function generateUniformsSync(group, uniformData) { - let textureCount = 1; - let func = `var value = null; - var cacheValue = null + let textureCount = 0; + let func = `var v = null; + var cv = null var gl = renderer.gl`; - for (const i in uniformData) + for (const i in group.uniforms) { const data = uniformData[i]; - // console.log(i, data.size); + + if(!data) + { + if(group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + ` + } + + continue; + } // TODO && uniformData[i].value !== 0 <-- do we still need this? if (data.type === 'float' && data.size === 1) { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; } - else if (data.type === 'sampler2D' && data.size === 1) + else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) { - func += `\nif (uniformValues.${i}.baseTexture) -{ - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount++}, false); + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - } -} -else -{ - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n` - uniformValues.${i}.bind(); -}`; + textureCount++; } else if (data.type === 'mat3' && data.size === 1) { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; + if(group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } } else if (data.type === 'vec2' && data.size === 1) { // TODO - do we need both here? // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; + if(group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + }\n`; + } } else { const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); + const template = templateType[data.type].replace('location', `ud.${i}.location`); - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; } } - //console.log(' --------------- ') - // console.log(func); + // console.log(' --------------- ') + // console.log(func); - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func } diff --git a/src/core/shader/generateUniformsSync2.js b/src/core/shader/generateUniformsSync2.js deleted file mode 100644 index 8a0fd6b..0000000 --- a/src/core/shader/generateUniformsSync2.js +++ /dev/null @@ -1,190 +0,0 @@ -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, - - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; - - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', - - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', - - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)', - sampler2DArray: 'gl.uniform1i(location, value)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, value)`, - - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', - - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', - - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', - - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)', - sampler2DArray: 'gl.uniform1iv(location, value)', -}; - -export default function generateUniformsSync2(group, uniformData) -{ - let textureCount = 0; - let func = `var value = null; - var cacheValue = null - var gl = renderer.gl`; - - - for (const i in group.uniforms) - { - const data = uniformData[i]; - //group.uniforms[i]; - // console.log(i, data); - if(!data) - { - if(group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uniformValues.${i}); - ` - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; - } - else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1) - { - func += `\nif (uniformValues.${i}.baseTexture) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } - - /* - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount}, false); - - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - }*/ -} -else if(uniformValues.${i}._new) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } -} -else -{ - // console.log("binding..") - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount}); - - uniformValues.${i}.bind(); -}`; - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; - -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); - - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; - } - } - - console.log(' --------------- ') - console.log(func); - - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 5b486ba..57b4b3c 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -7,6 +7,10 @@ import Buffer from './BatchBuffer'; import settings from '../../settings'; import bitTwiddle from 'bit-twiddle'; +import Geometry from '../../geometry/Geometry'; +//TODO rename this +import Buffer_GEOM from '../../geometry/Buffer'; + let TICK = 0; let TEXTURE_TICK = 0; @@ -65,6 +69,7 @@ * @member {Uint16Array} */ this.indices = createIndicesForQuads(this.size); + this.indexBuffer = new Buffer_GEOM(this.indices, true, true); /** * The default shaders that is used if a sprite doesn't have a more specific one. @@ -115,50 +120,27 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } - const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); - const sampleValues = new Int32Array(this.MAX_TEXTURES); - - for (let i = 0; i < this.MAX_TEXTURES; i++) - { - sampleValues[i] = i; - } - - shader.uniformGroup.add('default', {uSamplers:sampleValues}, true);//this.renderer.globalUniforms; - shader.uniforms.globals = this.renderer.globalUniforms; - - this.indexBuffer = GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // generate generateMultiTextureProgram, may be a better move? + this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. - - this.renderer.geometry.bindVao(null); - for (let i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + var buffer = new Buffer_GEOM(null, false); + /* eslint-disable max-len */ - - var attributeData = shader.program.attributeData; - // build the vao object that will render.. - this.vaos[i] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[i].addAttribute(this.vertexBuffers[i], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[i] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[i] = buffer; } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; - - this.boundTextures = new Array(this.MAX_TEXTURES); } /** @@ -221,9 +203,7 @@ const float32View = buffer.float32View; const uint32View = buffer.uint32View; - const boundTextures = this.boundTextures; - const rendererBoundTextures = this.renderer.boundTextures; - const touch = 0//this.renderer.textureGC.count; + const touch = 0;//this.renderer.textureGC.count; let index = 0; let nextTexture; @@ -244,13 +224,6 @@ let i; - // copy textures.. - for (i = 0; i < MAX_TEXTURES; ++i) - { - boundTextures[i] = rendererBoundTextures[i]; - boundTextures[i]._virtalBoundId = i; - } - for (i = 0; i < this.currentIndex; ++i) { // upload the sprite elemetns... @@ -371,37 +344,32 @@ if (this.vaoMax <= this.vertexCount) { this.vaoMax++; - this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + + let buffer = new Buffer_GEOM(null, false); /* eslint-disable max-len */ - - var attributeData = this.shader.program.attributeData; - - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[this.vertexCount].addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[this.vertexCount] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[this.vertexCount] = buffer; } - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); this.vertexCount++; } else { // lets use the faster option, always use buffer number 0 - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, true); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + + this.renderer.geometry.updateBuffers(); } /// render the groups.. @@ -409,15 +377,6 @@ var group = groups[i]; var groupTextureCount = group.textureCount; - //shader = this.shaders[groupTextureCount-1]; - - //if(!shader) - // { - // shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - //} - - /// this.renderer.shader.bind(shader); for (var j = 0; j < groupTextureCount; j++) { @@ -425,38 +384,11 @@ } // set the blend mode.. - //this.renderer.state.setBlendMode( group.blend ); + this.renderer.state.setBlendMode( group.blend ); gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); } -/* - // render the groups.. - for (i = 0; i < groupCount; ++i) - { - const group = groups[i]; - const groupTextureCount = group.textureCount; - for (let j = 0; j < groupTextureCount; j++) - { - currentTexture = group.textures[j]; - - // reset virtual ids.. - // lets do a quick check.. - if (rendererBoundTextures[group.ids[j]] !== currentTexture) - { - this.renderer.texture.bind(currentTexture, group.ids[j], true); - } - - // reset the virtualId.. - currentTexture._virtalBoundId = -1; - } - - // set the blend mode.. - this.renderer.state.setBlendMode(group.blend); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } -*/ // reset elements for the next flush this.currentIndex = 0; } @@ -466,16 +398,12 @@ */ start() { - // this.renderer._bindGLShader(this.shader); - this.renderer.shader.bind(this.shader, true); - this.renderer.shader.syncUniformGroup(this.shader.uniformGroup); + this.renderer.shader.bind(this.shader); if (settings.CAN_UPLOAD_SAME_BUFFER) { // bind buffer #0, we don't need others - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].bind(); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); } } diff --git a/src/core/sprites/webgl/generateMultiTextureShader.js b/src/core/sprites/webgl/generateMultiTextureShader.js index 1dabb7a..cab5255 100644 --- a/src/core/sprites/webgl/generateMultiTextureShader.js +++ b/src/core/sprites/webgl/generateMultiTextureShader.js @@ -1,5 +1,5 @@ -import { GLShader } from 'pixi-gl-core'; import Shader from '../../shader/Shader'; +import UniformGroup from '../../shader/UniformGroup'; import { PRECISION } from '../../const'; import { readFileSync } from 'fs'; import { join } from 'path'; @@ -20,25 +20,25 @@ export default function generateMultiTextureShader(gl, maxTextures) { - const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); - let fragmentSrc = fragTemplate; - - fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); - fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); - - const shaderold = new GLShader(gl, vertexSrc, fragmentSrc, PRECISION.DEFAULT); - const shader = Shader.from(vertexSrc, fragmentSrc);//, PRECISION.DEFAULT); - - const sampleValues = []; + const sampleValues = new Int32Array(maxTextures); for (let i = 0; i < maxTextures; i++) { sampleValues[i] = i; } - // shader.bind(); - // shader.uniforms.uSamplers = new Int32Array(sampleValues); - // shader.uniformGroup.static = true; + const uniforms = { + default:UniformGroup.from({uSamplers:sampleValues}, true), + } + + + const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); + let fragmentSrc = fragTemplate; + + fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); + fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); + + const shader = Shader.from(vertexSrc, fragmentSrc, uniforms);//, PRECISION.DEFAULT); return shader; } diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index ac37001..c5d09cd 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -142,6 +142,8 @@ { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); + + const isArray = uniformData.name.match(/\[.*?\]/, ''); const type = shaderUtils.mapType(gl, uniformData.type); // if (!name.match(maskRegex)) @@ -150,6 +152,7 @@ uniforms[name] = { type: type, size: uniformData.size, + isArray:isArray, value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index ad4fcfc..2f8dc56 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -40,54 +40,41 @@ // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniformGroup.uniforms[i]; - - if (!uniform) // bad as we need check wher ethe uniforms are.. + if (this.uniformGroup.uniforms[i] instanceof Array) { - //this.uniformGroup.uniforms[i] = program.uniformData[i].value; - } - else if (uniform instanceof Array) - { - this.uniformGroup.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(this.uniformGroup.uniforms[i]); } } } + checkUniformExists(name, group) + { + if(group.uniforms[name]) + { + return true; + } + + for (const i in group.uniforms) + { + const uniform = group.uniforms[i]; + + if(uniform.group) + { + if( this.checkUniformExists(name, uniform) ) + { + return true; + } + } + } + + return false; + } + get uniforms() { return this.uniformGroup.uniforms; } -/* -var offsetBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW); - -var colorBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); - -var instanceCount = 4; -var ext = gl.getExtension("ANGLE_instanced_arrays"); // Vendor prefixes may apply! - -// Bind the rest of the vertex attributes normally -bindMeshArrays(gl); - -// Bind the instance position data -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.enableVertexAttribArray(offsetLocation); -gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 12, 0); -ext.vertexAttribDivisorANGLE(offsetLocation, 1); // This makes it instanced! - -// Bind the instance color data -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.enableVertexAttribArray(colorLocation); -gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 16, 0); -ext.vertexAttribDivisorANGLE(colorLocation, 1); // This makes it instanced! - -// Draw the instanced meshes -ext.drawElementsInstancedANGLE(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount); -*/ /** * A short hand function to create a shader based of a vertex and fragment shader * diff --git a/src/core/shader/UniformGroup.js b/src/core/shader/UniformGroup.js index 8768226..e2ed22a 100644 --- a/src/core/shader/UniformGroup.js +++ b/src/core/shader/UniformGroup.js @@ -1,5 +1,3 @@ -import Program from './Program'; -import generateUniformsSync from './generateUniformsSync'; let UID = 0; diff --git a/src/core/shader/generateUniformsSync.js b/src/core/shader/generateUniformsSync.js index b7241ef..8a7cf55 100644 --- a/src/core/shader/generateUniformsSync.js +++ b/src/core/shader/generateUniformsSync.js @@ -1,153 +1,190 @@ + + +//cv = CachedValue +//v = value +//ud = uniformData +//uv = uniformValue +//l = loaction const GLSL_TO_SINGLE_SETTERS_CACHED = { - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, + float:` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; + vec2:` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', + vec3:` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + gl.uniform3f(location, v[0], v[1], v[2]) + }`, - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)' + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', }; const GLSL_TO_ARRAY_SETTERS = { - float: `gl.uniform1fv(location, value)`, + float: `gl.uniform1fv(location, v)`, - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)' + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', }; -export default function generateUniformsSync(uniformData) + +export default function generateUniformsSync(group, uniformData) { - let textureCount = 1; - let func = `var value = null; - var cacheValue = null + let textureCount = 0; + let func = `var v = null; + var cv = null var gl = renderer.gl`; - for (const i in uniformData) + for (const i in group.uniforms) { const data = uniformData[i]; - // console.log(i, data.size); + + if(!data) + { + if(group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + ` + } + + continue; + } // TODO && uniformData[i].value !== 0 <-- do we still need this? if (data.type === 'float' && data.size === 1) { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; } - else if (data.type === 'sampler2D' && data.size === 1) + else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) { - func += `\nif (uniformValues.${i}.baseTexture) -{ - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount++}, false); + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - } -} -else -{ - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n` - uniformValues.${i}.bind(); -}`; + textureCount++; } else if (data.type === 'mat3' && data.size === 1) { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; + if(group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } } else if (data.type === 'vec2' && data.size === 1) { // TODO - do we need both here? // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; + if(group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + }\n`; + } } else { const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); + const template = templateType[data.type].replace('location', `ud.${i}.location`); - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; } } - //console.log(' --------------- ') - // console.log(func); + // console.log(' --------------- ') + // console.log(func); - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func } diff --git a/src/core/shader/generateUniformsSync2.js b/src/core/shader/generateUniformsSync2.js deleted file mode 100644 index 8a0fd6b..0000000 --- a/src/core/shader/generateUniformsSync2.js +++ /dev/null @@ -1,190 +0,0 @@ -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, - - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; - - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', - - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', - - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)', - sampler2DArray: 'gl.uniform1i(location, value)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, value)`, - - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', - - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', - - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', - - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)', - sampler2DArray: 'gl.uniform1iv(location, value)', -}; - -export default function generateUniformsSync2(group, uniformData) -{ - let textureCount = 0; - let func = `var value = null; - var cacheValue = null - var gl = renderer.gl`; - - - for (const i in group.uniforms) - { - const data = uniformData[i]; - //group.uniforms[i]; - // console.log(i, data); - if(!data) - { - if(group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uniformValues.${i}); - ` - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; - } - else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1) - { - func += `\nif (uniformValues.${i}.baseTexture) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } - - /* - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount}, false); - - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - }*/ -} -else if(uniformValues.${i}._new) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } -} -else -{ - // console.log("binding..") - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount}); - - uniformValues.${i}.bind(); -}`; - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; - -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); - - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; - } - } - - console.log(' --------------- ') - console.log(func); - - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 5b486ba..57b4b3c 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -7,6 +7,10 @@ import Buffer from './BatchBuffer'; import settings from '../../settings'; import bitTwiddle from 'bit-twiddle'; +import Geometry from '../../geometry/Geometry'; +//TODO rename this +import Buffer_GEOM from '../../geometry/Buffer'; + let TICK = 0; let TEXTURE_TICK = 0; @@ -65,6 +69,7 @@ * @member {Uint16Array} */ this.indices = createIndicesForQuads(this.size); + this.indexBuffer = new Buffer_GEOM(this.indices, true, true); /** * The default shaders that is used if a sprite doesn't have a more specific one. @@ -115,50 +120,27 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } - const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); - const sampleValues = new Int32Array(this.MAX_TEXTURES); - - for (let i = 0; i < this.MAX_TEXTURES; i++) - { - sampleValues[i] = i; - } - - shader.uniformGroup.add('default', {uSamplers:sampleValues}, true);//this.renderer.globalUniforms; - shader.uniforms.globals = this.renderer.globalUniforms; - - this.indexBuffer = GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // generate generateMultiTextureProgram, may be a better move? + this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. - - this.renderer.geometry.bindVao(null); - for (let i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + var buffer = new Buffer_GEOM(null, false); + /* eslint-disable max-len */ - - var attributeData = shader.program.attributeData; - // build the vao object that will render.. - this.vaos[i] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[i].addAttribute(this.vertexBuffers[i], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[i] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[i] = buffer; } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; - - this.boundTextures = new Array(this.MAX_TEXTURES); } /** @@ -221,9 +203,7 @@ const float32View = buffer.float32View; const uint32View = buffer.uint32View; - const boundTextures = this.boundTextures; - const rendererBoundTextures = this.renderer.boundTextures; - const touch = 0//this.renderer.textureGC.count; + const touch = 0;//this.renderer.textureGC.count; let index = 0; let nextTexture; @@ -244,13 +224,6 @@ let i; - // copy textures.. - for (i = 0; i < MAX_TEXTURES; ++i) - { - boundTextures[i] = rendererBoundTextures[i]; - boundTextures[i]._virtalBoundId = i; - } - for (i = 0; i < this.currentIndex; ++i) { // upload the sprite elemetns... @@ -371,37 +344,32 @@ if (this.vaoMax <= this.vertexCount) { this.vaoMax++; - this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + + let buffer = new Buffer_GEOM(null, false); /* eslint-disable max-len */ - - var attributeData = this.shader.program.attributeData; - - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[this.vertexCount].addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[this.vertexCount] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[this.vertexCount] = buffer; } - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); this.vertexCount++; } else { // lets use the faster option, always use buffer number 0 - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, true); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + + this.renderer.geometry.updateBuffers(); } /// render the groups.. @@ -409,15 +377,6 @@ var group = groups[i]; var groupTextureCount = group.textureCount; - //shader = this.shaders[groupTextureCount-1]; - - //if(!shader) - // { - // shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - //} - - /// this.renderer.shader.bind(shader); for (var j = 0; j < groupTextureCount; j++) { @@ -425,38 +384,11 @@ } // set the blend mode.. - //this.renderer.state.setBlendMode( group.blend ); + this.renderer.state.setBlendMode( group.blend ); gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); } -/* - // render the groups.. - for (i = 0; i < groupCount; ++i) - { - const group = groups[i]; - const groupTextureCount = group.textureCount; - for (let j = 0; j < groupTextureCount; j++) - { - currentTexture = group.textures[j]; - - // reset virtual ids.. - // lets do a quick check.. - if (rendererBoundTextures[group.ids[j]] !== currentTexture) - { - this.renderer.texture.bind(currentTexture, group.ids[j], true); - } - - // reset the virtualId.. - currentTexture._virtalBoundId = -1; - } - - // set the blend mode.. - this.renderer.state.setBlendMode(group.blend); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } -*/ // reset elements for the next flush this.currentIndex = 0; } @@ -466,16 +398,12 @@ */ start() { - // this.renderer._bindGLShader(this.shader); - this.renderer.shader.bind(this.shader, true); - this.renderer.shader.syncUniformGroup(this.shader.uniformGroup); + this.renderer.shader.bind(this.shader); if (settings.CAN_UPLOAD_SAME_BUFFER) { // bind buffer #0, we don't need others - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].bind(); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); } } diff --git a/src/core/sprites/webgl/generateMultiTextureShader.js b/src/core/sprites/webgl/generateMultiTextureShader.js index 1dabb7a..cab5255 100644 --- a/src/core/sprites/webgl/generateMultiTextureShader.js +++ b/src/core/sprites/webgl/generateMultiTextureShader.js @@ -1,5 +1,5 @@ -import { GLShader } from 'pixi-gl-core'; import Shader from '../../shader/Shader'; +import UniformGroup from '../../shader/UniformGroup'; import { PRECISION } from '../../const'; import { readFileSync } from 'fs'; import { join } from 'path'; @@ -20,25 +20,25 @@ export default function generateMultiTextureShader(gl, maxTextures) { - const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); - let fragmentSrc = fragTemplate; - - fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); - fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); - - const shaderold = new GLShader(gl, vertexSrc, fragmentSrc, PRECISION.DEFAULT); - const shader = Shader.from(vertexSrc, fragmentSrc);//, PRECISION.DEFAULT); - - const sampleValues = []; + const sampleValues = new Int32Array(maxTextures); for (let i = 0; i < maxTextures; i++) { sampleValues[i] = i; } - // shader.bind(); - // shader.uniforms.uSamplers = new Int32Array(sampleValues); - // shader.uniformGroup.static = true; + const uniforms = { + default:UniformGroup.from({uSamplers:sampleValues}, true), + } + + + const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); + let fragmentSrc = fragTemplate; + + fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); + fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); + + const shader = Shader.from(vertexSrc, fragmentSrc, uniforms);//, PRECISION.DEFAULT); return shader; } diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 57c4cc5..13417a1 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -450,6 +450,7 @@ } } +BaseTexture.fromFrame = BaseTexture.fromFrame; BaseTexture.fromImage = BaseTexture.from; BaseTexture.fromSVG = BaseTexture.from; BaseTexture.fromCanvas = BaseTexture.from; \ No newline at end of file diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index ac37001..c5d09cd 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -142,6 +142,8 @@ { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); + + const isArray = uniformData.name.match(/\[.*?\]/, ''); const type = shaderUtils.mapType(gl, uniformData.type); // if (!name.match(maskRegex)) @@ -150,6 +152,7 @@ uniforms[name] = { type: type, size: uniformData.size, + isArray:isArray, value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index ad4fcfc..2f8dc56 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -40,54 +40,41 @@ // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniformGroup.uniforms[i]; - - if (!uniform) // bad as we need check wher ethe uniforms are.. + if (this.uniformGroup.uniforms[i] instanceof Array) { - //this.uniformGroup.uniforms[i] = program.uniformData[i].value; - } - else if (uniform instanceof Array) - { - this.uniformGroup.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(this.uniformGroup.uniforms[i]); } } } + checkUniformExists(name, group) + { + if(group.uniforms[name]) + { + return true; + } + + for (const i in group.uniforms) + { + const uniform = group.uniforms[i]; + + if(uniform.group) + { + if( this.checkUniformExists(name, uniform) ) + { + return true; + } + } + } + + return false; + } + get uniforms() { return this.uniformGroup.uniforms; } -/* -var offsetBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW); - -var colorBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); - -var instanceCount = 4; -var ext = gl.getExtension("ANGLE_instanced_arrays"); // Vendor prefixes may apply! - -// Bind the rest of the vertex attributes normally -bindMeshArrays(gl); - -// Bind the instance position data -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.enableVertexAttribArray(offsetLocation); -gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 12, 0); -ext.vertexAttribDivisorANGLE(offsetLocation, 1); // This makes it instanced! - -// Bind the instance color data -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.enableVertexAttribArray(colorLocation); -gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 16, 0); -ext.vertexAttribDivisorANGLE(colorLocation, 1); // This makes it instanced! - -// Draw the instanced meshes -ext.drawElementsInstancedANGLE(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount); -*/ /** * A short hand function to create a shader based of a vertex and fragment shader * diff --git a/src/core/shader/UniformGroup.js b/src/core/shader/UniformGroup.js index 8768226..e2ed22a 100644 --- a/src/core/shader/UniformGroup.js +++ b/src/core/shader/UniformGroup.js @@ -1,5 +1,3 @@ -import Program from './Program'; -import generateUniformsSync from './generateUniformsSync'; let UID = 0; diff --git a/src/core/shader/generateUniformsSync.js b/src/core/shader/generateUniformsSync.js index b7241ef..8a7cf55 100644 --- a/src/core/shader/generateUniformsSync.js +++ b/src/core/shader/generateUniformsSync.js @@ -1,153 +1,190 @@ + + +//cv = CachedValue +//v = value +//ud = uniformData +//uv = uniformValue +//l = loaction const GLSL_TO_SINGLE_SETTERS_CACHED = { - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, + float:` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; + vec2:` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', + vec3:` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + gl.uniform3f(location, v[0], v[1], v[2]) + }`, - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)' + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', }; const GLSL_TO_ARRAY_SETTERS = { - float: `gl.uniform1fv(location, value)`, + float: `gl.uniform1fv(location, v)`, - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)' + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', }; -export default function generateUniformsSync(uniformData) + +export default function generateUniformsSync(group, uniformData) { - let textureCount = 1; - let func = `var value = null; - var cacheValue = null + let textureCount = 0; + let func = `var v = null; + var cv = null var gl = renderer.gl`; - for (const i in uniformData) + for (const i in group.uniforms) { const data = uniformData[i]; - // console.log(i, data.size); + + if(!data) + { + if(group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + ` + } + + continue; + } // TODO && uniformData[i].value !== 0 <-- do we still need this? if (data.type === 'float' && data.size === 1) { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; } - else if (data.type === 'sampler2D' && data.size === 1) + else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) { - func += `\nif (uniformValues.${i}.baseTexture) -{ - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount++}, false); + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - } -} -else -{ - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n` - uniformValues.${i}.bind(); -}`; + textureCount++; } else if (data.type === 'mat3' && data.size === 1) { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; + if(group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } } else if (data.type === 'vec2' && data.size === 1) { // TODO - do we need both here? // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; + if(group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + }\n`; + } } else { const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); + const template = templateType[data.type].replace('location', `ud.${i}.location`); - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; } } - //console.log(' --------------- ') - // console.log(func); + // console.log(' --------------- ') + // console.log(func); - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func } diff --git a/src/core/shader/generateUniformsSync2.js b/src/core/shader/generateUniformsSync2.js deleted file mode 100644 index 8a0fd6b..0000000 --- a/src/core/shader/generateUniformsSync2.js +++ /dev/null @@ -1,190 +0,0 @@ -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, - - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; - - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', - - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', - - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)', - sampler2DArray: 'gl.uniform1i(location, value)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, value)`, - - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', - - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', - - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', - - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)', - sampler2DArray: 'gl.uniform1iv(location, value)', -}; - -export default function generateUniformsSync2(group, uniformData) -{ - let textureCount = 0; - let func = `var value = null; - var cacheValue = null - var gl = renderer.gl`; - - - for (const i in group.uniforms) - { - const data = uniformData[i]; - //group.uniforms[i]; - // console.log(i, data); - if(!data) - { - if(group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uniformValues.${i}); - ` - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; - } - else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1) - { - func += `\nif (uniformValues.${i}.baseTexture) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } - - /* - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount}, false); - - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - }*/ -} -else if(uniformValues.${i}._new) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } -} -else -{ - // console.log("binding..") - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount}); - - uniformValues.${i}.bind(); -}`; - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; - -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); - - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; - } - } - - console.log(' --------------- ') - console.log(func); - - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 5b486ba..57b4b3c 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -7,6 +7,10 @@ import Buffer from './BatchBuffer'; import settings from '../../settings'; import bitTwiddle from 'bit-twiddle'; +import Geometry from '../../geometry/Geometry'; +//TODO rename this +import Buffer_GEOM from '../../geometry/Buffer'; + let TICK = 0; let TEXTURE_TICK = 0; @@ -65,6 +69,7 @@ * @member {Uint16Array} */ this.indices = createIndicesForQuads(this.size); + this.indexBuffer = new Buffer_GEOM(this.indices, true, true); /** * The default shaders that is used if a sprite doesn't have a more specific one. @@ -115,50 +120,27 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } - const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); - const sampleValues = new Int32Array(this.MAX_TEXTURES); - - for (let i = 0; i < this.MAX_TEXTURES; i++) - { - sampleValues[i] = i; - } - - shader.uniformGroup.add('default', {uSamplers:sampleValues}, true);//this.renderer.globalUniforms; - shader.uniforms.globals = this.renderer.globalUniforms; - - this.indexBuffer = GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // generate generateMultiTextureProgram, may be a better move? + this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. - - this.renderer.geometry.bindVao(null); - for (let i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + var buffer = new Buffer_GEOM(null, false); + /* eslint-disable max-len */ - - var attributeData = shader.program.attributeData; - // build the vao object that will render.. - this.vaos[i] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[i].addAttribute(this.vertexBuffers[i], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[i] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[i] = buffer; } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; - - this.boundTextures = new Array(this.MAX_TEXTURES); } /** @@ -221,9 +203,7 @@ const float32View = buffer.float32View; const uint32View = buffer.uint32View; - const boundTextures = this.boundTextures; - const rendererBoundTextures = this.renderer.boundTextures; - const touch = 0//this.renderer.textureGC.count; + const touch = 0;//this.renderer.textureGC.count; let index = 0; let nextTexture; @@ -244,13 +224,6 @@ let i; - // copy textures.. - for (i = 0; i < MAX_TEXTURES; ++i) - { - boundTextures[i] = rendererBoundTextures[i]; - boundTextures[i]._virtalBoundId = i; - } - for (i = 0; i < this.currentIndex; ++i) { // upload the sprite elemetns... @@ -371,37 +344,32 @@ if (this.vaoMax <= this.vertexCount) { this.vaoMax++; - this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + + let buffer = new Buffer_GEOM(null, false); /* eslint-disable max-len */ - - var attributeData = this.shader.program.attributeData; - - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[this.vertexCount].addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[this.vertexCount] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[this.vertexCount] = buffer; } - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); this.vertexCount++; } else { // lets use the faster option, always use buffer number 0 - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, true); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + + this.renderer.geometry.updateBuffers(); } /// render the groups.. @@ -409,15 +377,6 @@ var group = groups[i]; var groupTextureCount = group.textureCount; - //shader = this.shaders[groupTextureCount-1]; - - //if(!shader) - // { - // shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - //} - - /// this.renderer.shader.bind(shader); for (var j = 0; j < groupTextureCount; j++) { @@ -425,38 +384,11 @@ } // set the blend mode.. - //this.renderer.state.setBlendMode( group.blend ); + this.renderer.state.setBlendMode( group.blend ); gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); } -/* - // render the groups.. - for (i = 0; i < groupCount; ++i) - { - const group = groups[i]; - const groupTextureCount = group.textureCount; - for (let j = 0; j < groupTextureCount; j++) - { - currentTexture = group.textures[j]; - - // reset virtual ids.. - // lets do a quick check.. - if (rendererBoundTextures[group.ids[j]] !== currentTexture) - { - this.renderer.texture.bind(currentTexture, group.ids[j], true); - } - - // reset the virtualId.. - currentTexture._virtalBoundId = -1; - } - - // set the blend mode.. - this.renderer.state.setBlendMode(group.blend); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } -*/ // reset elements for the next flush this.currentIndex = 0; } @@ -466,16 +398,12 @@ */ start() { - // this.renderer._bindGLShader(this.shader); - this.renderer.shader.bind(this.shader, true); - this.renderer.shader.syncUniformGroup(this.shader.uniformGroup); + this.renderer.shader.bind(this.shader); if (settings.CAN_UPLOAD_SAME_BUFFER) { // bind buffer #0, we don't need others - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].bind(); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); } } diff --git a/src/core/sprites/webgl/generateMultiTextureShader.js b/src/core/sprites/webgl/generateMultiTextureShader.js index 1dabb7a..cab5255 100644 --- a/src/core/sprites/webgl/generateMultiTextureShader.js +++ b/src/core/sprites/webgl/generateMultiTextureShader.js @@ -1,5 +1,5 @@ -import { GLShader } from 'pixi-gl-core'; import Shader from '../../shader/Shader'; +import UniformGroup from '../../shader/UniformGroup'; import { PRECISION } from '../../const'; import { readFileSync } from 'fs'; import { join } from 'path'; @@ -20,25 +20,25 @@ export default function generateMultiTextureShader(gl, maxTextures) { - const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); - let fragmentSrc = fragTemplate; - - fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); - fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); - - const shaderold = new GLShader(gl, vertexSrc, fragmentSrc, PRECISION.DEFAULT); - const shader = Shader.from(vertexSrc, fragmentSrc);//, PRECISION.DEFAULT); - - const sampleValues = []; + const sampleValues = new Int32Array(maxTextures); for (let i = 0; i < maxTextures; i++) { sampleValues[i] = i; } - // shader.bind(); - // shader.uniforms.uSamplers = new Int32Array(sampleValues); - // shader.uniformGroup.static = true; + const uniforms = { + default:UniformGroup.from({uSamplers:sampleValues}, true), + } + + + const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); + let fragmentSrc = fragTemplate; + + fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); + fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); + + const shader = Shader.from(vertexSrc, fragmentSrc, uniforms);//, PRECISION.DEFAULT); return shader; } diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 57c4cc5..13417a1 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -450,6 +450,7 @@ } } +BaseTexture.fromFrame = BaseTexture.fromFrame; BaseTexture.fromImage = BaseTexture.from; BaseTexture.fromSVG = BaseTexture.from; BaseTexture.fromCanvas = BaseTexture.from; \ No newline at end of file diff --git a/src/core/textures/resources/VideoResource.js b/src/core/textures/resources/VideoResource.js index 7d525c0..3402f96 100644 --- a/src/core/textures/resources/VideoResource.js +++ b/src/core/textures/resources/VideoResource.js @@ -58,6 +58,7 @@ update() { + //TODO - slow down and base on the videos framerate this.resourceUpdated.emit(); } diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index ac37001..c5d09cd 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -142,6 +142,8 @@ { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); + + const isArray = uniformData.name.match(/\[.*?\]/, ''); const type = shaderUtils.mapType(gl, uniformData.type); // if (!name.match(maskRegex)) @@ -150,6 +152,7 @@ uniforms[name] = { type: type, size: uniformData.size, + isArray:isArray, value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index ad4fcfc..2f8dc56 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -40,54 +40,41 @@ // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniformGroup.uniforms[i]; - - if (!uniform) // bad as we need check wher ethe uniforms are.. + if (this.uniformGroup.uniforms[i] instanceof Array) { - //this.uniformGroup.uniforms[i] = program.uniformData[i].value; - } - else if (uniform instanceof Array) - { - this.uniformGroup.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(this.uniformGroup.uniforms[i]); } } } + checkUniformExists(name, group) + { + if(group.uniforms[name]) + { + return true; + } + + for (const i in group.uniforms) + { + const uniform = group.uniforms[i]; + + if(uniform.group) + { + if( this.checkUniformExists(name, uniform) ) + { + return true; + } + } + } + + return false; + } + get uniforms() { return this.uniformGroup.uniforms; } -/* -var offsetBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW); - -var colorBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); - -var instanceCount = 4; -var ext = gl.getExtension("ANGLE_instanced_arrays"); // Vendor prefixes may apply! - -// Bind the rest of the vertex attributes normally -bindMeshArrays(gl); - -// Bind the instance position data -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.enableVertexAttribArray(offsetLocation); -gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 12, 0); -ext.vertexAttribDivisorANGLE(offsetLocation, 1); // This makes it instanced! - -// Bind the instance color data -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.enableVertexAttribArray(colorLocation); -gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 16, 0); -ext.vertexAttribDivisorANGLE(colorLocation, 1); // This makes it instanced! - -// Draw the instanced meshes -ext.drawElementsInstancedANGLE(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount); -*/ /** * A short hand function to create a shader based of a vertex and fragment shader * diff --git a/src/core/shader/UniformGroup.js b/src/core/shader/UniformGroup.js index 8768226..e2ed22a 100644 --- a/src/core/shader/UniformGroup.js +++ b/src/core/shader/UniformGroup.js @@ -1,5 +1,3 @@ -import Program from './Program'; -import generateUniformsSync from './generateUniformsSync'; let UID = 0; diff --git a/src/core/shader/generateUniformsSync.js b/src/core/shader/generateUniformsSync.js index b7241ef..8a7cf55 100644 --- a/src/core/shader/generateUniformsSync.js +++ b/src/core/shader/generateUniformsSync.js @@ -1,153 +1,190 @@ + + +//cv = CachedValue +//v = value +//ud = uniformData +//uv = uniformValue +//l = loaction const GLSL_TO_SINGLE_SETTERS_CACHED = { - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, + float:` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; + vec2:` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', + vec3:` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + gl.uniform3f(location, v[0], v[1], v[2]) + }`, - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)' + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', }; const GLSL_TO_ARRAY_SETTERS = { - float: `gl.uniform1fv(location, value)`, + float: `gl.uniform1fv(location, v)`, - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)' + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', }; -export default function generateUniformsSync(uniformData) + +export default function generateUniformsSync(group, uniformData) { - let textureCount = 1; - let func = `var value = null; - var cacheValue = null + let textureCount = 0; + let func = `var v = null; + var cv = null var gl = renderer.gl`; - for (const i in uniformData) + for (const i in group.uniforms) { const data = uniformData[i]; - // console.log(i, data.size); + + if(!data) + { + if(group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + ` + } + + continue; + } // TODO && uniformData[i].value !== 0 <-- do we still need this? if (data.type === 'float' && data.size === 1) { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; } - else if (data.type === 'sampler2D' && data.size === 1) + else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) { - func += `\nif (uniformValues.${i}.baseTexture) -{ - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount++}, false); + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - } -} -else -{ - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n` - uniformValues.${i}.bind(); -}`; + textureCount++; } else if (data.type === 'mat3' && data.size === 1) { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; + if(group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } } else if (data.type === 'vec2' && data.size === 1) { // TODO - do we need both here? // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; + if(group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + }\n`; + } } else { const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); + const template = templateType[data.type].replace('location', `ud.${i}.location`); - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; } } - //console.log(' --------------- ') - // console.log(func); + // console.log(' --------------- ') + // console.log(func); - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func } diff --git a/src/core/shader/generateUniformsSync2.js b/src/core/shader/generateUniformsSync2.js deleted file mode 100644 index 8a0fd6b..0000000 --- a/src/core/shader/generateUniformsSync2.js +++ /dev/null @@ -1,190 +0,0 @@ -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, - - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; - - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', - - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', - - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)', - sampler2DArray: 'gl.uniform1i(location, value)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, value)`, - - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', - - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', - - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', - - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)', - sampler2DArray: 'gl.uniform1iv(location, value)', -}; - -export default function generateUniformsSync2(group, uniformData) -{ - let textureCount = 0; - let func = `var value = null; - var cacheValue = null - var gl = renderer.gl`; - - - for (const i in group.uniforms) - { - const data = uniformData[i]; - //group.uniforms[i]; - // console.log(i, data); - if(!data) - { - if(group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uniformValues.${i}); - ` - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; - } - else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1) - { - func += `\nif (uniformValues.${i}.baseTexture) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } - - /* - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount}, false); - - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - }*/ -} -else if(uniformValues.${i}._new) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } -} -else -{ - // console.log("binding..") - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount}); - - uniformValues.${i}.bind(); -}`; - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; - -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); - - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; - } - } - - console.log(' --------------- ') - console.log(func); - - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 5b486ba..57b4b3c 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -7,6 +7,10 @@ import Buffer from './BatchBuffer'; import settings from '../../settings'; import bitTwiddle from 'bit-twiddle'; +import Geometry from '../../geometry/Geometry'; +//TODO rename this +import Buffer_GEOM from '../../geometry/Buffer'; + let TICK = 0; let TEXTURE_TICK = 0; @@ -65,6 +69,7 @@ * @member {Uint16Array} */ this.indices = createIndicesForQuads(this.size); + this.indexBuffer = new Buffer_GEOM(this.indices, true, true); /** * The default shaders that is used if a sprite doesn't have a more specific one. @@ -115,50 +120,27 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } - const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); - const sampleValues = new Int32Array(this.MAX_TEXTURES); - - for (let i = 0; i < this.MAX_TEXTURES; i++) - { - sampleValues[i] = i; - } - - shader.uniformGroup.add('default', {uSamplers:sampleValues}, true);//this.renderer.globalUniforms; - shader.uniforms.globals = this.renderer.globalUniforms; - - this.indexBuffer = GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // generate generateMultiTextureProgram, may be a better move? + this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. - - this.renderer.geometry.bindVao(null); - for (let i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + var buffer = new Buffer_GEOM(null, false); + /* eslint-disable max-len */ - - var attributeData = shader.program.attributeData; - // build the vao object that will render.. - this.vaos[i] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[i].addAttribute(this.vertexBuffers[i], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[i] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[i] = buffer; } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; - - this.boundTextures = new Array(this.MAX_TEXTURES); } /** @@ -221,9 +203,7 @@ const float32View = buffer.float32View; const uint32View = buffer.uint32View; - const boundTextures = this.boundTextures; - const rendererBoundTextures = this.renderer.boundTextures; - const touch = 0//this.renderer.textureGC.count; + const touch = 0;//this.renderer.textureGC.count; let index = 0; let nextTexture; @@ -244,13 +224,6 @@ let i; - // copy textures.. - for (i = 0; i < MAX_TEXTURES; ++i) - { - boundTextures[i] = rendererBoundTextures[i]; - boundTextures[i]._virtalBoundId = i; - } - for (i = 0; i < this.currentIndex; ++i) { // upload the sprite elemetns... @@ -371,37 +344,32 @@ if (this.vaoMax <= this.vertexCount) { this.vaoMax++; - this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + + let buffer = new Buffer_GEOM(null, false); /* eslint-disable max-len */ - - var attributeData = this.shader.program.attributeData; - - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[this.vertexCount].addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[this.vertexCount] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[this.vertexCount] = buffer; } - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); this.vertexCount++; } else { // lets use the faster option, always use buffer number 0 - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, true); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + + this.renderer.geometry.updateBuffers(); } /// render the groups.. @@ -409,15 +377,6 @@ var group = groups[i]; var groupTextureCount = group.textureCount; - //shader = this.shaders[groupTextureCount-1]; - - //if(!shader) - // { - // shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - //} - - /// this.renderer.shader.bind(shader); for (var j = 0; j < groupTextureCount; j++) { @@ -425,38 +384,11 @@ } // set the blend mode.. - //this.renderer.state.setBlendMode( group.blend ); + this.renderer.state.setBlendMode( group.blend ); gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); } -/* - // render the groups.. - for (i = 0; i < groupCount; ++i) - { - const group = groups[i]; - const groupTextureCount = group.textureCount; - for (let j = 0; j < groupTextureCount; j++) - { - currentTexture = group.textures[j]; - - // reset virtual ids.. - // lets do a quick check.. - if (rendererBoundTextures[group.ids[j]] !== currentTexture) - { - this.renderer.texture.bind(currentTexture, group.ids[j], true); - } - - // reset the virtualId.. - currentTexture._virtalBoundId = -1; - } - - // set the blend mode.. - this.renderer.state.setBlendMode(group.blend); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } -*/ // reset elements for the next flush this.currentIndex = 0; } @@ -466,16 +398,12 @@ */ start() { - // this.renderer._bindGLShader(this.shader); - this.renderer.shader.bind(this.shader, true); - this.renderer.shader.syncUniformGroup(this.shader.uniformGroup); + this.renderer.shader.bind(this.shader); if (settings.CAN_UPLOAD_SAME_BUFFER) { // bind buffer #0, we don't need others - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].bind(); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); } } diff --git a/src/core/sprites/webgl/generateMultiTextureShader.js b/src/core/sprites/webgl/generateMultiTextureShader.js index 1dabb7a..cab5255 100644 --- a/src/core/sprites/webgl/generateMultiTextureShader.js +++ b/src/core/sprites/webgl/generateMultiTextureShader.js @@ -1,5 +1,5 @@ -import { GLShader } from 'pixi-gl-core'; import Shader from '../../shader/Shader'; +import UniformGroup from '../../shader/UniformGroup'; import { PRECISION } from '../../const'; import { readFileSync } from 'fs'; import { join } from 'path'; @@ -20,25 +20,25 @@ export default function generateMultiTextureShader(gl, maxTextures) { - const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); - let fragmentSrc = fragTemplate; - - fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); - fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); - - const shaderold = new GLShader(gl, vertexSrc, fragmentSrc, PRECISION.DEFAULT); - const shader = Shader.from(vertexSrc, fragmentSrc);//, PRECISION.DEFAULT); - - const sampleValues = []; + const sampleValues = new Int32Array(maxTextures); for (let i = 0; i < maxTextures; i++) { sampleValues[i] = i; } - // shader.bind(); - // shader.uniforms.uSamplers = new Int32Array(sampleValues); - // shader.uniformGroup.static = true; + const uniforms = { + default:UniformGroup.from({uSamplers:sampleValues}, true), + } + + + const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); + let fragmentSrc = fragTemplate; + + fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); + fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); + + const shader = Shader.from(vertexSrc, fragmentSrc, uniforms);//, PRECISION.DEFAULT); return shader; } diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 57c4cc5..13417a1 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -450,6 +450,7 @@ } } +BaseTexture.fromFrame = BaseTexture.fromFrame; BaseTexture.fromImage = BaseTexture.from; BaseTexture.fromSVG = BaseTexture.from; BaseTexture.fromCanvas = BaseTexture.from; \ No newline at end of file diff --git a/src/core/textures/resources/VideoResource.js b/src/core/textures/resources/VideoResource.js index 7d525c0..3402f96 100644 --- a/src/core/textures/resources/VideoResource.js +++ b/src/core/textures/resources/VideoResource.js @@ -58,6 +58,7 @@ update() { + //TODO - slow down and base on the videos framerate this.resourceUpdated.emit(); } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 3fdd996..609da80 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -252,7 +252,7 @@ if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) { const args = [ - `\n %c %c %c Pixi.js ${VERSION} - ✰ ${type} ✰ %c %c http://www.pixijs.com/ %c %c ♥%c♥%c♥ \n\n`, + `\n %c %c %c Pixi.js ${VERSION} - ✰✰ ${type} ✰✰ %c %c http://www.pixijs.com/ %c %c ♥%c♥%c♥ \n\n`, 'background: #ff66a5; padding:5px 0;', 'background: #ff66a5; padding:5px 0;', 'color: #ff66a5; background: #030307; padding:5px 0;', diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index ac37001..c5d09cd 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -142,6 +142,8 @@ { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); + + const isArray = uniformData.name.match(/\[.*?\]/, ''); const type = shaderUtils.mapType(gl, uniformData.type); // if (!name.match(maskRegex)) @@ -150,6 +152,7 @@ uniforms[name] = { type: type, size: uniformData.size, + isArray:isArray, value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index ad4fcfc..2f8dc56 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -40,54 +40,41 @@ // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniformGroup.uniforms[i]; - - if (!uniform) // bad as we need check wher ethe uniforms are.. + if (this.uniformGroup.uniforms[i] instanceof Array) { - //this.uniformGroup.uniforms[i] = program.uniformData[i].value; - } - else if (uniform instanceof Array) - { - this.uniformGroup.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(this.uniformGroup.uniforms[i]); } } } + checkUniformExists(name, group) + { + if(group.uniforms[name]) + { + return true; + } + + for (const i in group.uniforms) + { + const uniform = group.uniforms[i]; + + if(uniform.group) + { + if( this.checkUniformExists(name, uniform) ) + { + return true; + } + } + } + + return false; + } + get uniforms() { return this.uniformGroup.uniforms; } -/* -var offsetBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW); - -var colorBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); - -var instanceCount = 4; -var ext = gl.getExtension("ANGLE_instanced_arrays"); // Vendor prefixes may apply! - -// Bind the rest of the vertex attributes normally -bindMeshArrays(gl); - -// Bind the instance position data -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.enableVertexAttribArray(offsetLocation); -gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 12, 0); -ext.vertexAttribDivisorANGLE(offsetLocation, 1); // This makes it instanced! - -// Bind the instance color data -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.enableVertexAttribArray(colorLocation); -gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 16, 0); -ext.vertexAttribDivisorANGLE(colorLocation, 1); // This makes it instanced! - -// Draw the instanced meshes -ext.drawElementsInstancedANGLE(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount); -*/ /** * A short hand function to create a shader based of a vertex and fragment shader * diff --git a/src/core/shader/UniformGroup.js b/src/core/shader/UniformGroup.js index 8768226..e2ed22a 100644 --- a/src/core/shader/UniformGroup.js +++ b/src/core/shader/UniformGroup.js @@ -1,5 +1,3 @@ -import Program from './Program'; -import generateUniformsSync from './generateUniformsSync'; let UID = 0; diff --git a/src/core/shader/generateUniformsSync.js b/src/core/shader/generateUniformsSync.js index b7241ef..8a7cf55 100644 --- a/src/core/shader/generateUniformsSync.js +++ b/src/core/shader/generateUniformsSync.js @@ -1,153 +1,190 @@ + + +//cv = CachedValue +//v = value +//ud = uniformData +//uv = uniformValue +//l = loaction const GLSL_TO_SINGLE_SETTERS_CACHED = { - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, + float:` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; + vec2:` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', + vec3:` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + gl.uniform3f(location, v[0], v[1], v[2]) + }`, - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)' + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', }; const GLSL_TO_ARRAY_SETTERS = { - float: `gl.uniform1fv(location, value)`, + float: `gl.uniform1fv(location, v)`, - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)' + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', }; -export default function generateUniformsSync(uniformData) + +export default function generateUniformsSync(group, uniformData) { - let textureCount = 1; - let func = `var value = null; - var cacheValue = null + let textureCount = 0; + let func = `var v = null; + var cv = null var gl = renderer.gl`; - for (const i in uniformData) + for (const i in group.uniforms) { const data = uniformData[i]; - // console.log(i, data.size); + + if(!data) + { + if(group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + ` + } + + continue; + } // TODO && uniformData[i].value !== 0 <-- do we still need this? if (data.type === 'float' && data.size === 1) { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; } - else if (data.type === 'sampler2D' && data.size === 1) + else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) { - func += `\nif (uniformValues.${i}.baseTexture) -{ - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount++}, false); + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - } -} -else -{ - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n` - uniformValues.${i}.bind(); -}`; + textureCount++; } else if (data.type === 'mat3' && data.size === 1) { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; + if(group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } } else if (data.type === 'vec2' && data.size === 1) { // TODO - do we need both here? // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; + if(group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + }\n`; + } } else { const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); + const template = templateType[data.type].replace('location', `ud.${i}.location`); - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; } } - //console.log(' --------------- ') - // console.log(func); + // console.log(' --------------- ') + // console.log(func); - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func } diff --git a/src/core/shader/generateUniformsSync2.js b/src/core/shader/generateUniformsSync2.js deleted file mode 100644 index 8a0fd6b..0000000 --- a/src/core/shader/generateUniformsSync2.js +++ /dev/null @@ -1,190 +0,0 @@ -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, - - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; - - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', - - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', - - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)', - sampler2DArray: 'gl.uniform1i(location, value)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, value)`, - - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', - - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', - - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', - - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)', - sampler2DArray: 'gl.uniform1iv(location, value)', -}; - -export default function generateUniformsSync2(group, uniformData) -{ - let textureCount = 0; - let func = `var value = null; - var cacheValue = null - var gl = renderer.gl`; - - - for (const i in group.uniforms) - { - const data = uniformData[i]; - //group.uniforms[i]; - // console.log(i, data); - if(!data) - { - if(group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uniformValues.${i}); - ` - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; - } - else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1) - { - func += `\nif (uniformValues.${i}.baseTexture) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } - - /* - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount}, false); - - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - }*/ -} -else if(uniformValues.${i}._new) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } -} -else -{ - // console.log("binding..") - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount}); - - uniformValues.${i}.bind(); -}`; - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; - -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); - - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; - } - } - - console.log(' --------------- ') - console.log(func); - - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 5b486ba..57b4b3c 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -7,6 +7,10 @@ import Buffer from './BatchBuffer'; import settings from '../../settings'; import bitTwiddle from 'bit-twiddle'; +import Geometry from '../../geometry/Geometry'; +//TODO rename this +import Buffer_GEOM from '../../geometry/Buffer'; + let TICK = 0; let TEXTURE_TICK = 0; @@ -65,6 +69,7 @@ * @member {Uint16Array} */ this.indices = createIndicesForQuads(this.size); + this.indexBuffer = new Buffer_GEOM(this.indices, true, true); /** * The default shaders that is used if a sprite doesn't have a more specific one. @@ -115,50 +120,27 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } - const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); - const sampleValues = new Int32Array(this.MAX_TEXTURES); - - for (let i = 0; i < this.MAX_TEXTURES; i++) - { - sampleValues[i] = i; - } - - shader.uniformGroup.add('default', {uSamplers:sampleValues}, true);//this.renderer.globalUniforms; - shader.uniforms.globals = this.renderer.globalUniforms; - - this.indexBuffer = GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // generate generateMultiTextureProgram, may be a better move? + this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. - - this.renderer.geometry.bindVao(null); - for (let i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + var buffer = new Buffer_GEOM(null, false); + /* eslint-disable max-len */ - - var attributeData = shader.program.attributeData; - // build the vao object that will render.. - this.vaos[i] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[i].addAttribute(this.vertexBuffers[i], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[i] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[i] = buffer; } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; - - this.boundTextures = new Array(this.MAX_TEXTURES); } /** @@ -221,9 +203,7 @@ const float32View = buffer.float32View; const uint32View = buffer.uint32View; - const boundTextures = this.boundTextures; - const rendererBoundTextures = this.renderer.boundTextures; - const touch = 0//this.renderer.textureGC.count; + const touch = 0;//this.renderer.textureGC.count; let index = 0; let nextTexture; @@ -244,13 +224,6 @@ let i; - // copy textures.. - for (i = 0; i < MAX_TEXTURES; ++i) - { - boundTextures[i] = rendererBoundTextures[i]; - boundTextures[i]._virtalBoundId = i; - } - for (i = 0; i < this.currentIndex; ++i) { // upload the sprite elemetns... @@ -371,37 +344,32 @@ if (this.vaoMax <= this.vertexCount) { this.vaoMax++; - this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + + let buffer = new Buffer_GEOM(null, false); /* eslint-disable max-len */ - - var attributeData = this.shader.program.attributeData; - - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[this.vertexCount].addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[this.vertexCount] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[this.vertexCount] = buffer; } - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); this.vertexCount++; } else { // lets use the faster option, always use buffer number 0 - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, true); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + + this.renderer.geometry.updateBuffers(); } /// render the groups.. @@ -409,15 +377,6 @@ var group = groups[i]; var groupTextureCount = group.textureCount; - //shader = this.shaders[groupTextureCount-1]; - - //if(!shader) - // { - // shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - //} - - /// this.renderer.shader.bind(shader); for (var j = 0; j < groupTextureCount; j++) { @@ -425,38 +384,11 @@ } // set the blend mode.. - //this.renderer.state.setBlendMode( group.blend ); + this.renderer.state.setBlendMode( group.blend ); gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); } -/* - // render the groups.. - for (i = 0; i < groupCount; ++i) - { - const group = groups[i]; - const groupTextureCount = group.textureCount; - for (let j = 0; j < groupTextureCount; j++) - { - currentTexture = group.textures[j]; - - // reset virtual ids.. - // lets do a quick check.. - if (rendererBoundTextures[group.ids[j]] !== currentTexture) - { - this.renderer.texture.bind(currentTexture, group.ids[j], true); - } - - // reset the virtualId.. - currentTexture._virtalBoundId = -1; - } - - // set the blend mode.. - this.renderer.state.setBlendMode(group.blend); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } -*/ // reset elements for the next flush this.currentIndex = 0; } @@ -466,16 +398,12 @@ */ start() { - // this.renderer._bindGLShader(this.shader); - this.renderer.shader.bind(this.shader, true); - this.renderer.shader.syncUniformGroup(this.shader.uniformGroup); + this.renderer.shader.bind(this.shader); if (settings.CAN_UPLOAD_SAME_BUFFER) { // bind buffer #0, we don't need others - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].bind(); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); } } diff --git a/src/core/sprites/webgl/generateMultiTextureShader.js b/src/core/sprites/webgl/generateMultiTextureShader.js index 1dabb7a..cab5255 100644 --- a/src/core/sprites/webgl/generateMultiTextureShader.js +++ b/src/core/sprites/webgl/generateMultiTextureShader.js @@ -1,5 +1,5 @@ -import { GLShader } from 'pixi-gl-core'; import Shader from '../../shader/Shader'; +import UniformGroup from '../../shader/UniformGroup'; import { PRECISION } from '../../const'; import { readFileSync } from 'fs'; import { join } from 'path'; @@ -20,25 +20,25 @@ export default function generateMultiTextureShader(gl, maxTextures) { - const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); - let fragmentSrc = fragTemplate; - - fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); - fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); - - const shaderold = new GLShader(gl, vertexSrc, fragmentSrc, PRECISION.DEFAULT); - const shader = Shader.from(vertexSrc, fragmentSrc);//, PRECISION.DEFAULT); - - const sampleValues = []; + const sampleValues = new Int32Array(maxTextures); for (let i = 0; i < maxTextures; i++) { sampleValues[i] = i; } - // shader.bind(); - // shader.uniforms.uSamplers = new Int32Array(sampleValues); - // shader.uniformGroup.static = true; + const uniforms = { + default:UniformGroup.from({uSamplers:sampleValues}, true), + } + + + const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); + let fragmentSrc = fragTemplate; + + fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); + fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); + + const shader = Shader.from(vertexSrc, fragmentSrc, uniforms);//, PRECISION.DEFAULT); return shader; } diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 57c4cc5..13417a1 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -450,6 +450,7 @@ } } +BaseTexture.fromFrame = BaseTexture.fromFrame; BaseTexture.fromImage = BaseTexture.from; BaseTexture.fromSVG = BaseTexture.from; BaseTexture.fromCanvas = BaseTexture.from; \ No newline at end of file diff --git a/src/core/textures/resources/VideoResource.js b/src/core/textures/resources/VideoResource.js index 7d525c0..3402f96 100644 --- a/src/core/textures/resources/VideoResource.js +++ b/src/core/textures/resources/VideoResource.js @@ -58,6 +58,7 @@ update() { + //TODO - slow down and base on the videos framerate this.resourceUpdated.emit(); } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 3fdd996..609da80 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -252,7 +252,7 @@ if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) { const args = [ - `\n %c %c %c Pixi.js ${VERSION} - ✰ ${type} ✰ %c %c http://www.pixijs.com/ %c %c ♥%c♥%c♥ \n\n`, + `\n %c %c %c Pixi.js ${VERSION} - ✰✰ ${type} ✰✰ %c %c http://www.pixijs.com/ %c %c ♥%c♥%c♥ \n\n`, 'background: #ff66a5; padding:5px 0;', 'background: #ff66a5; padding:5px 0;', 'color: #ff66a5; background: #030307; padding:5px 0;', diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index dd11031..ec46e24 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import RawMesh from './RawMesh'; -import Geometry from './geometry/Geometry'; +import Geometry from '../core/geometry/Geometry'; import * as core from '../core'; import { readFileSync } from 'fs'; import { join } from 'path'; diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index ac37001..c5d09cd 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -142,6 +142,8 @@ { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); + + const isArray = uniformData.name.match(/\[.*?\]/, ''); const type = shaderUtils.mapType(gl, uniformData.type); // if (!name.match(maskRegex)) @@ -150,6 +152,7 @@ uniforms[name] = { type: type, size: uniformData.size, + isArray:isArray, value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index ad4fcfc..2f8dc56 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -40,54 +40,41 @@ // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniformGroup.uniforms[i]; - - if (!uniform) // bad as we need check wher ethe uniforms are.. + if (this.uniformGroup.uniforms[i] instanceof Array) { - //this.uniformGroup.uniforms[i] = program.uniformData[i].value; - } - else if (uniform instanceof Array) - { - this.uniformGroup.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(this.uniformGroup.uniforms[i]); } } } + checkUniformExists(name, group) + { + if(group.uniforms[name]) + { + return true; + } + + for (const i in group.uniforms) + { + const uniform = group.uniforms[i]; + + if(uniform.group) + { + if( this.checkUniformExists(name, uniform) ) + { + return true; + } + } + } + + return false; + } + get uniforms() { return this.uniformGroup.uniforms; } -/* -var offsetBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW); - -var colorBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); - -var instanceCount = 4; -var ext = gl.getExtension("ANGLE_instanced_arrays"); // Vendor prefixes may apply! - -// Bind the rest of the vertex attributes normally -bindMeshArrays(gl); - -// Bind the instance position data -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.enableVertexAttribArray(offsetLocation); -gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 12, 0); -ext.vertexAttribDivisorANGLE(offsetLocation, 1); // This makes it instanced! - -// Bind the instance color data -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.enableVertexAttribArray(colorLocation); -gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 16, 0); -ext.vertexAttribDivisorANGLE(colorLocation, 1); // This makes it instanced! - -// Draw the instanced meshes -ext.drawElementsInstancedANGLE(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount); -*/ /** * A short hand function to create a shader based of a vertex and fragment shader * diff --git a/src/core/shader/UniformGroup.js b/src/core/shader/UniformGroup.js index 8768226..e2ed22a 100644 --- a/src/core/shader/UniformGroup.js +++ b/src/core/shader/UniformGroup.js @@ -1,5 +1,3 @@ -import Program from './Program'; -import generateUniformsSync from './generateUniformsSync'; let UID = 0; diff --git a/src/core/shader/generateUniformsSync.js b/src/core/shader/generateUniformsSync.js index b7241ef..8a7cf55 100644 --- a/src/core/shader/generateUniformsSync.js +++ b/src/core/shader/generateUniformsSync.js @@ -1,153 +1,190 @@ + + +//cv = CachedValue +//v = value +//ud = uniformData +//uv = uniformValue +//l = loaction const GLSL_TO_SINGLE_SETTERS_CACHED = { - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, + float:` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; + vec2:` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', + vec3:` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + gl.uniform3f(location, v[0], v[1], v[2]) + }`, - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)' + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', }; const GLSL_TO_ARRAY_SETTERS = { - float: `gl.uniform1fv(location, value)`, + float: `gl.uniform1fv(location, v)`, - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)' + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', }; -export default function generateUniformsSync(uniformData) + +export default function generateUniformsSync(group, uniformData) { - let textureCount = 1; - let func = `var value = null; - var cacheValue = null + let textureCount = 0; + let func = `var v = null; + var cv = null var gl = renderer.gl`; - for (const i in uniformData) + for (const i in group.uniforms) { const data = uniformData[i]; - // console.log(i, data.size); + + if(!data) + { + if(group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + ` + } + + continue; + } // TODO && uniformData[i].value !== 0 <-- do we still need this? if (data.type === 'float' && data.size === 1) { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; } - else if (data.type === 'sampler2D' && data.size === 1) + else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) { - func += `\nif (uniformValues.${i}.baseTexture) -{ - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount++}, false); + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - } -} -else -{ - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n` - uniformValues.${i}.bind(); -}`; + textureCount++; } else if (data.type === 'mat3' && data.size === 1) { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; + if(group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } } else if (data.type === 'vec2' && data.size === 1) { // TODO - do we need both here? // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; + if(group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + }\n`; + } } else { const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); + const template = templateType[data.type].replace('location', `ud.${i}.location`); - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; } } - //console.log(' --------------- ') - // console.log(func); + // console.log(' --------------- ') + // console.log(func); - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func } diff --git a/src/core/shader/generateUniformsSync2.js b/src/core/shader/generateUniformsSync2.js deleted file mode 100644 index 8a0fd6b..0000000 --- a/src/core/shader/generateUniformsSync2.js +++ /dev/null @@ -1,190 +0,0 @@ -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, - - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; - - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', - - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', - - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)', - sampler2DArray: 'gl.uniform1i(location, value)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, value)`, - - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', - - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', - - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', - - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)', - sampler2DArray: 'gl.uniform1iv(location, value)', -}; - -export default function generateUniformsSync2(group, uniformData) -{ - let textureCount = 0; - let func = `var value = null; - var cacheValue = null - var gl = renderer.gl`; - - - for (const i in group.uniforms) - { - const data = uniformData[i]; - //group.uniforms[i]; - // console.log(i, data); - if(!data) - { - if(group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uniformValues.${i}); - ` - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; - } - else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1) - { - func += `\nif (uniformValues.${i}.baseTexture) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } - - /* - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount}, false); - - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - }*/ -} -else if(uniformValues.${i}._new) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } -} -else -{ - // console.log("binding..") - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount}); - - uniformValues.${i}.bind(); -}`; - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; - -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); - - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; - } - } - - console.log(' --------------- ') - console.log(func); - - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 5b486ba..57b4b3c 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -7,6 +7,10 @@ import Buffer from './BatchBuffer'; import settings from '../../settings'; import bitTwiddle from 'bit-twiddle'; +import Geometry from '../../geometry/Geometry'; +//TODO rename this +import Buffer_GEOM from '../../geometry/Buffer'; + let TICK = 0; let TEXTURE_TICK = 0; @@ -65,6 +69,7 @@ * @member {Uint16Array} */ this.indices = createIndicesForQuads(this.size); + this.indexBuffer = new Buffer_GEOM(this.indices, true, true); /** * The default shaders that is used if a sprite doesn't have a more specific one. @@ -115,50 +120,27 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } - const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); - const sampleValues = new Int32Array(this.MAX_TEXTURES); - - for (let i = 0; i < this.MAX_TEXTURES; i++) - { - sampleValues[i] = i; - } - - shader.uniformGroup.add('default', {uSamplers:sampleValues}, true);//this.renderer.globalUniforms; - shader.uniforms.globals = this.renderer.globalUniforms; - - this.indexBuffer = GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // generate generateMultiTextureProgram, may be a better move? + this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. - - this.renderer.geometry.bindVao(null); - for (let i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + var buffer = new Buffer_GEOM(null, false); + /* eslint-disable max-len */ - - var attributeData = shader.program.attributeData; - // build the vao object that will render.. - this.vaos[i] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[i].addAttribute(this.vertexBuffers[i], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[i] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[i] = buffer; } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; - - this.boundTextures = new Array(this.MAX_TEXTURES); } /** @@ -221,9 +203,7 @@ const float32View = buffer.float32View; const uint32View = buffer.uint32View; - const boundTextures = this.boundTextures; - const rendererBoundTextures = this.renderer.boundTextures; - const touch = 0//this.renderer.textureGC.count; + const touch = 0;//this.renderer.textureGC.count; let index = 0; let nextTexture; @@ -244,13 +224,6 @@ let i; - // copy textures.. - for (i = 0; i < MAX_TEXTURES; ++i) - { - boundTextures[i] = rendererBoundTextures[i]; - boundTextures[i]._virtalBoundId = i; - } - for (i = 0; i < this.currentIndex; ++i) { // upload the sprite elemetns... @@ -371,37 +344,32 @@ if (this.vaoMax <= this.vertexCount) { this.vaoMax++; - this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + + let buffer = new Buffer_GEOM(null, false); /* eslint-disable max-len */ - - var attributeData = this.shader.program.attributeData; - - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[this.vertexCount].addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[this.vertexCount] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[this.vertexCount] = buffer; } - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); this.vertexCount++; } else { // lets use the faster option, always use buffer number 0 - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, true); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + + this.renderer.geometry.updateBuffers(); } /// render the groups.. @@ -409,15 +377,6 @@ var group = groups[i]; var groupTextureCount = group.textureCount; - //shader = this.shaders[groupTextureCount-1]; - - //if(!shader) - // { - // shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - //} - - /// this.renderer.shader.bind(shader); for (var j = 0; j < groupTextureCount; j++) { @@ -425,38 +384,11 @@ } // set the blend mode.. - //this.renderer.state.setBlendMode( group.blend ); + this.renderer.state.setBlendMode( group.blend ); gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); } -/* - // render the groups.. - for (i = 0; i < groupCount; ++i) - { - const group = groups[i]; - const groupTextureCount = group.textureCount; - for (let j = 0; j < groupTextureCount; j++) - { - currentTexture = group.textures[j]; - - // reset virtual ids.. - // lets do a quick check.. - if (rendererBoundTextures[group.ids[j]] !== currentTexture) - { - this.renderer.texture.bind(currentTexture, group.ids[j], true); - } - - // reset the virtualId.. - currentTexture._virtalBoundId = -1; - } - - // set the blend mode.. - this.renderer.state.setBlendMode(group.blend); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } -*/ // reset elements for the next flush this.currentIndex = 0; } @@ -466,16 +398,12 @@ */ start() { - // this.renderer._bindGLShader(this.shader); - this.renderer.shader.bind(this.shader, true); - this.renderer.shader.syncUniformGroup(this.shader.uniformGroup); + this.renderer.shader.bind(this.shader); if (settings.CAN_UPLOAD_SAME_BUFFER) { // bind buffer #0, we don't need others - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].bind(); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); } } diff --git a/src/core/sprites/webgl/generateMultiTextureShader.js b/src/core/sprites/webgl/generateMultiTextureShader.js index 1dabb7a..cab5255 100644 --- a/src/core/sprites/webgl/generateMultiTextureShader.js +++ b/src/core/sprites/webgl/generateMultiTextureShader.js @@ -1,5 +1,5 @@ -import { GLShader } from 'pixi-gl-core'; import Shader from '../../shader/Shader'; +import UniformGroup from '../../shader/UniformGroup'; import { PRECISION } from '../../const'; import { readFileSync } from 'fs'; import { join } from 'path'; @@ -20,25 +20,25 @@ export default function generateMultiTextureShader(gl, maxTextures) { - const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); - let fragmentSrc = fragTemplate; - - fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); - fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); - - const shaderold = new GLShader(gl, vertexSrc, fragmentSrc, PRECISION.DEFAULT); - const shader = Shader.from(vertexSrc, fragmentSrc);//, PRECISION.DEFAULT); - - const sampleValues = []; + const sampleValues = new Int32Array(maxTextures); for (let i = 0; i < maxTextures; i++) { sampleValues[i] = i; } - // shader.bind(); - // shader.uniforms.uSamplers = new Int32Array(sampleValues); - // shader.uniformGroup.static = true; + const uniforms = { + default:UniformGroup.from({uSamplers:sampleValues}, true), + } + + + const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); + let fragmentSrc = fragTemplate; + + fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); + fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); + + const shader = Shader.from(vertexSrc, fragmentSrc, uniforms);//, PRECISION.DEFAULT); return shader; } diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 57c4cc5..13417a1 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -450,6 +450,7 @@ } } +BaseTexture.fromFrame = BaseTexture.fromFrame; BaseTexture.fromImage = BaseTexture.from; BaseTexture.fromSVG = BaseTexture.from; BaseTexture.fromCanvas = BaseTexture.from; \ No newline at end of file diff --git a/src/core/textures/resources/VideoResource.js b/src/core/textures/resources/VideoResource.js index 7d525c0..3402f96 100644 --- a/src/core/textures/resources/VideoResource.js +++ b/src/core/textures/resources/VideoResource.js @@ -58,6 +58,7 @@ update() { + //TODO - slow down and base on the videos framerate this.resourceUpdated.emit(); } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 3fdd996..609da80 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -252,7 +252,7 @@ if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) { const args = [ - `\n %c %c %c Pixi.js ${VERSION} - ✰ ${type} ✰ %c %c http://www.pixijs.com/ %c %c ♥%c♥%c♥ \n\n`, + `\n %c %c %c Pixi.js ${VERSION} - ✰✰ ${type} ✰✰ %c %c http://www.pixijs.com/ %c %c ♥%c♥%c♥ \n\n`, 'background: #ff66a5; padding:5px 0;', 'background: #ff66a5; padding:5px 0;', 'color: #ff66a5; background: #030307; padding:5px 0;', diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index dd11031..ec46e24 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import RawMesh from './RawMesh'; -import Geometry from './geometry/Geometry'; +import Geometry from '../core/geometry/Geometry'; import * as core from '../core'; import { readFileSync } from 'fs'; import { join } from 'path'; diff --git a/src/mesh/geometry/Attribute.js b/src/mesh/geometry/Attribute.js deleted file mode 100644 index 83f92c2..0000000 --- a/src/mesh/geometry/Attribute.js +++ /dev/null @@ -1,58 +0,0 @@ -/* eslint-disable max-len */ - -/** - * holds the information for a single attribute structure required to render geometry. - * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} - * This can include anything from positions, uvs, normals, colors etc.. - * - * @class - * @memberof PIXI.mesh.Attribute - */ -class Attribute -{ - /** - * @param {string} buffer the id of the buffer that this attribute will look for - * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. - * @param {Boolean} [normalised=false] should the data be normalised. - * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available - * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) - * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) - */ - constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) - { - this.buffer = buffer; - this.size = size; - this.normalized = normalised; - this.type = type; - this.stride = stride; - this.start = start; - this.instance = instance; - } - - /** - * Destroys the Attribute. - */ - destroy() - { - this.buffer = null; - } - - /** - * Helper function that creates an Attribute based on the information provided - * - * @static - * @param {string} buffer the id of the buffer that this attribute will look for - * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 - * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) - * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) - * @param {Boolean} [normalised=false] should the data be normalised. - * - * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided - */ - static from(buffer, size, stride, start, normalised) - { - return new Attribute(buffer, size, stride, start, normalised); - } -} - -module.exports = Attribute; diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index ac37001..c5d09cd 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -142,6 +142,8 @@ { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); + + const isArray = uniformData.name.match(/\[.*?\]/, ''); const type = shaderUtils.mapType(gl, uniformData.type); // if (!name.match(maskRegex)) @@ -150,6 +152,7 @@ uniforms[name] = { type: type, size: uniformData.size, + isArray:isArray, value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index ad4fcfc..2f8dc56 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -40,54 +40,41 @@ // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniformGroup.uniforms[i]; - - if (!uniform) // bad as we need check wher ethe uniforms are.. + if (this.uniformGroup.uniforms[i] instanceof Array) { - //this.uniformGroup.uniforms[i] = program.uniformData[i].value; - } - else if (uniform instanceof Array) - { - this.uniformGroup.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(this.uniformGroup.uniforms[i]); } } } + checkUniformExists(name, group) + { + if(group.uniforms[name]) + { + return true; + } + + for (const i in group.uniforms) + { + const uniform = group.uniforms[i]; + + if(uniform.group) + { + if( this.checkUniformExists(name, uniform) ) + { + return true; + } + } + } + + return false; + } + get uniforms() { return this.uniformGroup.uniforms; } -/* -var offsetBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW); - -var colorBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); - -var instanceCount = 4; -var ext = gl.getExtension("ANGLE_instanced_arrays"); // Vendor prefixes may apply! - -// Bind the rest of the vertex attributes normally -bindMeshArrays(gl); - -// Bind the instance position data -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.enableVertexAttribArray(offsetLocation); -gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 12, 0); -ext.vertexAttribDivisorANGLE(offsetLocation, 1); // This makes it instanced! - -// Bind the instance color data -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.enableVertexAttribArray(colorLocation); -gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 16, 0); -ext.vertexAttribDivisorANGLE(colorLocation, 1); // This makes it instanced! - -// Draw the instanced meshes -ext.drawElementsInstancedANGLE(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount); -*/ /** * A short hand function to create a shader based of a vertex and fragment shader * diff --git a/src/core/shader/UniformGroup.js b/src/core/shader/UniformGroup.js index 8768226..e2ed22a 100644 --- a/src/core/shader/UniformGroup.js +++ b/src/core/shader/UniformGroup.js @@ -1,5 +1,3 @@ -import Program from './Program'; -import generateUniformsSync from './generateUniformsSync'; let UID = 0; diff --git a/src/core/shader/generateUniformsSync.js b/src/core/shader/generateUniformsSync.js index b7241ef..8a7cf55 100644 --- a/src/core/shader/generateUniformsSync.js +++ b/src/core/shader/generateUniformsSync.js @@ -1,153 +1,190 @@ + + +//cv = CachedValue +//v = value +//ud = uniformData +//uv = uniformValue +//l = loaction const GLSL_TO_SINGLE_SETTERS_CACHED = { - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, + float:` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; + vec2:` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', + vec3:` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + gl.uniform3f(location, v[0], v[1], v[2]) + }`, - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)' + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', }; const GLSL_TO_ARRAY_SETTERS = { - float: `gl.uniform1fv(location, value)`, + float: `gl.uniform1fv(location, v)`, - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)' + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', }; -export default function generateUniformsSync(uniformData) + +export default function generateUniformsSync(group, uniformData) { - let textureCount = 1; - let func = `var value = null; - var cacheValue = null + let textureCount = 0; + let func = `var v = null; + var cv = null var gl = renderer.gl`; - for (const i in uniformData) + for (const i in group.uniforms) { const data = uniformData[i]; - // console.log(i, data.size); + + if(!data) + { + if(group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + ` + } + + continue; + } // TODO && uniformData[i].value !== 0 <-- do we still need this? if (data.type === 'float' && data.size === 1) { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; } - else if (data.type === 'sampler2D' && data.size === 1) + else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) { - func += `\nif (uniformValues.${i}.baseTexture) -{ - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount++}, false); + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - } -} -else -{ - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n` - uniformValues.${i}.bind(); -}`; + textureCount++; } else if (data.type === 'mat3' && data.size === 1) { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; + if(group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } } else if (data.type === 'vec2' && data.size === 1) { // TODO - do we need both here? // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; + if(group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + }\n`; + } } else { const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); + const template = templateType[data.type].replace('location', `ud.${i}.location`); - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; } } - //console.log(' --------------- ') - // console.log(func); + // console.log(' --------------- ') + // console.log(func); - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func } diff --git a/src/core/shader/generateUniformsSync2.js b/src/core/shader/generateUniformsSync2.js deleted file mode 100644 index 8a0fd6b..0000000 --- a/src/core/shader/generateUniformsSync2.js +++ /dev/null @@ -1,190 +0,0 @@ -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, - - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; - - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', - - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', - - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)', - sampler2DArray: 'gl.uniform1i(location, value)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, value)`, - - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', - - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', - - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', - - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)', - sampler2DArray: 'gl.uniform1iv(location, value)', -}; - -export default function generateUniformsSync2(group, uniformData) -{ - let textureCount = 0; - let func = `var value = null; - var cacheValue = null - var gl = renderer.gl`; - - - for (const i in group.uniforms) - { - const data = uniformData[i]; - //group.uniforms[i]; - // console.log(i, data); - if(!data) - { - if(group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uniformValues.${i}); - ` - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; - } - else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1) - { - func += `\nif (uniformValues.${i}.baseTexture) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } - - /* - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount}, false); - - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - }*/ -} -else if(uniformValues.${i}._new) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } -} -else -{ - // console.log("binding..") - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount}); - - uniformValues.${i}.bind(); -}`; - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; - -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); - - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; - } - } - - console.log(' --------------- ') - console.log(func); - - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 5b486ba..57b4b3c 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -7,6 +7,10 @@ import Buffer from './BatchBuffer'; import settings from '../../settings'; import bitTwiddle from 'bit-twiddle'; +import Geometry from '../../geometry/Geometry'; +//TODO rename this +import Buffer_GEOM from '../../geometry/Buffer'; + let TICK = 0; let TEXTURE_TICK = 0; @@ -65,6 +69,7 @@ * @member {Uint16Array} */ this.indices = createIndicesForQuads(this.size); + this.indexBuffer = new Buffer_GEOM(this.indices, true, true); /** * The default shaders that is used if a sprite doesn't have a more specific one. @@ -115,50 +120,27 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } - const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); - const sampleValues = new Int32Array(this.MAX_TEXTURES); - - for (let i = 0; i < this.MAX_TEXTURES; i++) - { - sampleValues[i] = i; - } - - shader.uniformGroup.add('default', {uSamplers:sampleValues}, true);//this.renderer.globalUniforms; - shader.uniforms.globals = this.renderer.globalUniforms; - - this.indexBuffer = GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // generate generateMultiTextureProgram, may be a better move? + this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. - - this.renderer.geometry.bindVao(null); - for (let i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + var buffer = new Buffer_GEOM(null, false); + /* eslint-disable max-len */ - - var attributeData = shader.program.attributeData; - // build the vao object that will render.. - this.vaos[i] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[i].addAttribute(this.vertexBuffers[i], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[i] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[i] = buffer; } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; - - this.boundTextures = new Array(this.MAX_TEXTURES); } /** @@ -221,9 +203,7 @@ const float32View = buffer.float32View; const uint32View = buffer.uint32View; - const boundTextures = this.boundTextures; - const rendererBoundTextures = this.renderer.boundTextures; - const touch = 0//this.renderer.textureGC.count; + const touch = 0;//this.renderer.textureGC.count; let index = 0; let nextTexture; @@ -244,13 +224,6 @@ let i; - // copy textures.. - for (i = 0; i < MAX_TEXTURES; ++i) - { - boundTextures[i] = rendererBoundTextures[i]; - boundTextures[i]._virtalBoundId = i; - } - for (i = 0; i < this.currentIndex; ++i) { // upload the sprite elemetns... @@ -371,37 +344,32 @@ if (this.vaoMax <= this.vertexCount) { this.vaoMax++; - this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + + let buffer = new Buffer_GEOM(null, false); /* eslint-disable max-len */ - - var attributeData = this.shader.program.attributeData; - - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[this.vertexCount].addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[this.vertexCount] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[this.vertexCount] = buffer; } - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); this.vertexCount++; } else { // lets use the faster option, always use buffer number 0 - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, true); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + + this.renderer.geometry.updateBuffers(); } /// render the groups.. @@ -409,15 +377,6 @@ var group = groups[i]; var groupTextureCount = group.textureCount; - //shader = this.shaders[groupTextureCount-1]; - - //if(!shader) - // { - // shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - //} - - /// this.renderer.shader.bind(shader); for (var j = 0; j < groupTextureCount; j++) { @@ -425,38 +384,11 @@ } // set the blend mode.. - //this.renderer.state.setBlendMode( group.blend ); + this.renderer.state.setBlendMode( group.blend ); gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); } -/* - // render the groups.. - for (i = 0; i < groupCount; ++i) - { - const group = groups[i]; - const groupTextureCount = group.textureCount; - for (let j = 0; j < groupTextureCount; j++) - { - currentTexture = group.textures[j]; - - // reset virtual ids.. - // lets do a quick check.. - if (rendererBoundTextures[group.ids[j]] !== currentTexture) - { - this.renderer.texture.bind(currentTexture, group.ids[j], true); - } - - // reset the virtualId.. - currentTexture._virtalBoundId = -1; - } - - // set the blend mode.. - this.renderer.state.setBlendMode(group.blend); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } -*/ // reset elements for the next flush this.currentIndex = 0; } @@ -466,16 +398,12 @@ */ start() { - // this.renderer._bindGLShader(this.shader); - this.renderer.shader.bind(this.shader, true); - this.renderer.shader.syncUniformGroup(this.shader.uniformGroup); + this.renderer.shader.bind(this.shader); if (settings.CAN_UPLOAD_SAME_BUFFER) { // bind buffer #0, we don't need others - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].bind(); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); } } diff --git a/src/core/sprites/webgl/generateMultiTextureShader.js b/src/core/sprites/webgl/generateMultiTextureShader.js index 1dabb7a..cab5255 100644 --- a/src/core/sprites/webgl/generateMultiTextureShader.js +++ b/src/core/sprites/webgl/generateMultiTextureShader.js @@ -1,5 +1,5 @@ -import { GLShader } from 'pixi-gl-core'; import Shader from '../../shader/Shader'; +import UniformGroup from '../../shader/UniformGroup'; import { PRECISION } from '../../const'; import { readFileSync } from 'fs'; import { join } from 'path'; @@ -20,25 +20,25 @@ export default function generateMultiTextureShader(gl, maxTextures) { - const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); - let fragmentSrc = fragTemplate; - - fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); - fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); - - const shaderold = new GLShader(gl, vertexSrc, fragmentSrc, PRECISION.DEFAULT); - const shader = Shader.from(vertexSrc, fragmentSrc);//, PRECISION.DEFAULT); - - const sampleValues = []; + const sampleValues = new Int32Array(maxTextures); for (let i = 0; i < maxTextures; i++) { sampleValues[i] = i; } - // shader.bind(); - // shader.uniforms.uSamplers = new Int32Array(sampleValues); - // shader.uniformGroup.static = true; + const uniforms = { + default:UniformGroup.from({uSamplers:sampleValues}, true), + } + + + const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); + let fragmentSrc = fragTemplate; + + fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); + fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); + + const shader = Shader.from(vertexSrc, fragmentSrc, uniforms);//, PRECISION.DEFAULT); return shader; } diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 57c4cc5..13417a1 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -450,6 +450,7 @@ } } +BaseTexture.fromFrame = BaseTexture.fromFrame; BaseTexture.fromImage = BaseTexture.from; BaseTexture.fromSVG = BaseTexture.from; BaseTexture.fromCanvas = BaseTexture.from; \ No newline at end of file diff --git a/src/core/textures/resources/VideoResource.js b/src/core/textures/resources/VideoResource.js index 7d525c0..3402f96 100644 --- a/src/core/textures/resources/VideoResource.js +++ b/src/core/textures/resources/VideoResource.js @@ -58,6 +58,7 @@ update() { + //TODO - slow down and base on the videos framerate this.resourceUpdated.emit(); } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 3fdd996..609da80 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -252,7 +252,7 @@ if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) { const args = [ - `\n %c %c %c Pixi.js ${VERSION} - ✰ ${type} ✰ %c %c http://www.pixijs.com/ %c %c ♥%c♥%c♥ \n\n`, + `\n %c %c %c Pixi.js ${VERSION} - ✰✰ ${type} ✰✰ %c %c http://www.pixijs.com/ %c %c ♥%c♥%c♥ \n\n`, 'background: #ff66a5; padding:5px 0;', 'background: #ff66a5; padding:5px 0;', 'color: #ff66a5; background: #030307; padding:5px 0;', diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index dd11031..ec46e24 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import RawMesh from './RawMesh'; -import Geometry from './geometry/Geometry'; +import Geometry from '../core/geometry/Geometry'; import * as core from '../core'; import { readFileSync } from 'fs'; import { join } from 'path'; diff --git a/src/mesh/geometry/Attribute.js b/src/mesh/geometry/Attribute.js deleted file mode 100644 index 83f92c2..0000000 --- a/src/mesh/geometry/Attribute.js +++ /dev/null @@ -1,58 +0,0 @@ -/* eslint-disable max-len */ - -/** - * holds the information for a single attribute structure required to render geometry. - * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} - * This can include anything from positions, uvs, normals, colors etc.. - * - * @class - * @memberof PIXI.mesh.Attribute - */ -class Attribute -{ - /** - * @param {string} buffer the id of the buffer that this attribute will look for - * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. - * @param {Boolean} [normalised=false] should the data be normalised. - * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available - * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) - * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) - */ - constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) - { - this.buffer = buffer; - this.size = size; - this.normalized = normalised; - this.type = type; - this.stride = stride; - this.start = start; - this.instance = instance; - } - - /** - * Destroys the Attribute. - */ - destroy() - { - this.buffer = null; - } - - /** - * Helper function that creates an Attribute based on the information provided - * - * @static - * @param {string} buffer the id of the buffer that this attribute will look for - * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 - * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) - * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) - * @param {Boolean} [normalised=false] should the data be normalised. - * - * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided - */ - static from(buffer, size, stride, start, normalised) - { - return new Attribute(buffer, size, stride, start, normalised); - } -} - -module.exports = Attribute; diff --git a/src/mesh/geometry/Buffer.js b/src/mesh/geometry/Buffer.js deleted file mode 100644 index 43d49bf..0000000 --- a/src/mesh/geometry/Buffer.js +++ /dev/null @@ -1,80 +0,0 @@ -let UID = 0; -/* eslint-disable max-len */ - -/** - * A wrapper for data so that it can be used and uploaded by webGL - * - * @class - * @memberof PIXI - */ -export default class Buffer -{ - /** - * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. - */ - constructor(data) - { - /** - * The data in the buffer, as a typed array - * - * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray - */ - this.data = data; - - /** - * A map of renderer IDs to webgl buffer - * - * @private - * @member {object} - */ - this._glBuffers = []; - - this._updateID = 0; - - this.index = false; - - this.static = true; - - this.id = UID++; - } - - // TODO could explore flagging only a partial upload? - /** - * flags this buffer as requiring an upload to the GPU - */ - update() - { - this._updateID++; - } - - /** - * Destroys the buffer - */ - destroy() - { - for (let i = 0; i < this._glBuffers.length; i++) - { - this._glBuffers[i].destroy(); - } - - this.data = null; - } - - /** - * Helper function that creates a buffer based on an array or TypedArray - * - * @static - * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. - * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. - */ - static from(data) - { - if (data instanceof Array) - { - data = new Float32Array(data); - } - - return new Buffer(data); - } -} - diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index ac37001..c5d09cd 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -142,6 +142,8 @@ { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); + + const isArray = uniformData.name.match(/\[.*?\]/, ''); const type = shaderUtils.mapType(gl, uniformData.type); // if (!name.match(maskRegex)) @@ -150,6 +152,7 @@ uniforms[name] = { type: type, size: uniformData.size, + isArray:isArray, value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index ad4fcfc..2f8dc56 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -40,54 +40,41 @@ // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniformGroup.uniforms[i]; - - if (!uniform) // bad as we need check wher ethe uniforms are.. + if (this.uniformGroup.uniforms[i] instanceof Array) { - //this.uniformGroup.uniforms[i] = program.uniformData[i].value; - } - else if (uniform instanceof Array) - { - this.uniformGroup.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(this.uniformGroup.uniforms[i]); } } } + checkUniformExists(name, group) + { + if(group.uniforms[name]) + { + return true; + } + + for (const i in group.uniforms) + { + const uniform = group.uniforms[i]; + + if(uniform.group) + { + if( this.checkUniformExists(name, uniform) ) + { + return true; + } + } + } + + return false; + } + get uniforms() { return this.uniformGroup.uniforms; } -/* -var offsetBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW); - -var colorBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); - -var instanceCount = 4; -var ext = gl.getExtension("ANGLE_instanced_arrays"); // Vendor prefixes may apply! - -// Bind the rest of the vertex attributes normally -bindMeshArrays(gl); - -// Bind the instance position data -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.enableVertexAttribArray(offsetLocation); -gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 12, 0); -ext.vertexAttribDivisorANGLE(offsetLocation, 1); // This makes it instanced! - -// Bind the instance color data -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.enableVertexAttribArray(colorLocation); -gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 16, 0); -ext.vertexAttribDivisorANGLE(colorLocation, 1); // This makes it instanced! - -// Draw the instanced meshes -ext.drawElementsInstancedANGLE(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount); -*/ /** * A short hand function to create a shader based of a vertex and fragment shader * diff --git a/src/core/shader/UniformGroup.js b/src/core/shader/UniformGroup.js index 8768226..e2ed22a 100644 --- a/src/core/shader/UniformGroup.js +++ b/src/core/shader/UniformGroup.js @@ -1,5 +1,3 @@ -import Program from './Program'; -import generateUniformsSync from './generateUniformsSync'; let UID = 0; diff --git a/src/core/shader/generateUniformsSync.js b/src/core/shader/generateUniformsSync.js index b7241ef..8a7cf55 100644 --- a/src/core/shader/generateUniformsSync.js +++ b/src/core/shader/generateUniformsSync.js @@ -1,153 +1,190 @@ + + +//cv = CachedValue +//v = value +//ud = uniformData +//uv = uniformValue +//l = loaction const GLSL_TO_SINGLE_SETTERS_CACHED = { - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, + float:` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; + vec2:` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', + vec3:` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + gl.uniform3f(location, v[0], v[1], v[2]) + }`, - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)' + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', }; const GLSL_TO_ARRAY_SETTERS = { - float: `gl.uniform1fv(location, value)`, + float: `gl.uniform1fv(location, v)`, - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)' + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', }; -export default function generateUniformsSync(uniformData) + +export default function generateUniformsSync(group, uniformData) { - let textureCount = 1; - let func = `var value = null; - var cacheValue = null + let textureCount = 0; + let func = `var v = null; + var cv = null var gl = renderer.gl`; - for (const i in uniformData) + for (const i in group.uniforms) { const data = uniformData[i]; - // console.log(i, data.size); + + if(!data) + { + if(group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + ` + } + + continue; + } // TODO && uniformData[i].value !== 0 <-- do we still need this? if (data.type === 'float' && data.size === 1) { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; } - else if (data.type === 'sampler2D' && data.size === 1) + else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) { - func += `\nif (uniformValues.${i}.baseTexture) -{ - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount++}, false); + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - } -} -else -{ - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n` - uniformValues.${i}.bind(); -}`; + textureCount++; } else if (data.type === 'mat3' && data.size === 1) { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; + if(group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } } else if (data.type === 'vec2' && data.size === 1) { // TODO - do we need both here? // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; + if(group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + }\n`; + } } else { const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); + const template = templateType[data.type].replace('location', `ud.${i}.location`); - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; } } - //console.log(' --------------- ') - // console.log(func); + // console.log(' --------------- ') + // console.log(func); - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func } diff --git a/src/core/shader/generateUniformsSync2.js b/src/core/shader/generateUniformsSync2.js deleted file mode 100644 index 8a0fd6b..0000000 --- a/src/core/shader/generateUniformsSync2.js +++ /dev/null @@ -1,190 +0,0 @@ -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, - - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; - - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', - - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', - - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)', - sampler2DArray: 'gl.uniform1i(location, value)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, value)`, - - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', - - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', - - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', - - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)', - sampler2DArray: 'gl.uniform1iv(location, value)', -}; - -export default function generateUniformsSync2(group, uniformData) -{ - let textureCount = 0; - let func = `var value = null; - var cacheValue = null - var gl = renderer.gl`; - - - for (const i in group.uniforms) - { - const data = uniformData[i]; - //group.uniforms[i]; - // console.log(i, data); - if(!data) - { - if(group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uniformValues.${i}); - ` - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; - } - else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1) - { - func += `\nif (uniformValues.${i}.baseTexture) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } - - /* - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount}, false); - - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - }*/ -} -else if(uniformValues.${i}._new) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } -} -else -{ - // console.log("binding..") - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount}); - - uniformValues.${i}.bind(); -}`; - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; - -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); - - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; - } - } - - console.log(' --------------- ') - console.log(func); - - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 5b486ba..57b4b3c 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -7,6 +7,10 @@ import Buffer from './BatchBuffer'; import settings from '../../settings'; import bitTwiddle from 'bit-twiddle'; +import Geometry from '../../geometry/Geometry'; +//TODO rename this +import Buffer_GEOM from '../../geometry/Buffer'; + let TICK = 0; let TEXTURE_TICK = 0; @@ -65,6 +69,7 @@ * @member {Uint16Array} */ this.indices = createIndicesForQuads(this.size); + this.indexBuffer = new Buffer_GEOM(this.indices, true, true); /** * The default shaders that is used if a sprite doesn't have a more specific one. @@ -115,50 +120,27 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } - const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); - const sampleValues = new Int32Array(this.MAX_TEXTURES); - - for (let i = 0; i < this.MAX_TEXTURES; i++) - { - sampleValues[i] = i; - } - - shader.uniformGroup.add('default', {uSamplers:sampleValues}, true);//this.renderer.globalUniforms; - shader.uniforms.globals = this.renderer.globalUniforms; - - this.indexBuffer = GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // generate generateMultiTextureProgram, may be a better move? + this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. - - this.renderer.geometry.bindVao(null); - for (let i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + var buffer = new Buffer_GEOM(null, false); + /* eslint-disable max-len */ - - var attributeData = shader.program.attributeData; - // build the vao object that will render.. - this.vaos[i] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[i].addAttribute(this.vertexBuffers[i], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[i] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[i] = buffer; } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; - - this.boundTextures = new Array(this.MAX_TEXTURES); } /** @@ -221,9 +203,7 @@ const float32View = buffer.float32View; const uint32View = buffer.uint32View; - const boundTextures = this.boundTextures; - const rendererBoundTextures = this.renderer.boundTextures; - const touch = 0//this.renderer.textureGC.count; + const touch = 0;//this.renderer.textureGC.count; let index = 0; let nextTexture; @@ -244,13 +224,6 @@ let i; - // copy textures.. - for (i = 0; i < MAX_TEXTURES; ++i) - { - boundTextures[i] = rendererBoundTextures[i]; - boundTextures[i]._virtalBoundId = i; - } - for (i = 0; i < this.currentIndex; ++i) { // upload the sprite elemetns... @@ -371,37 +344,32 @@ if (this.vaoMax <= this.vertexCount) { this.vaoMax++; - this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + + let buffer = new Buffer_GEOM(null, false); /* eslint-disable max-len */ - - var attributeData = this.shader.program.attributeData; - - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[this.vertexCount].addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[this.vertexCount] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[this.vertexCount] = buffer; } - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); this.vertexCount++; } else { // lets use the faster option, always use buffer number 0 - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, true); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + + this.renderer.geometry.updateBuffers(); } /// render the groups.. @@ -409,15 +377,6 @@ var group = groups[i]; var groupTextureCount = group.textureCount; - //shader = this.shaders[groupTextureCount-1]; - - //if(!shader) - // { - // shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - //} - - /// this.renderer.shader.bind(shader); for (var j = 0; j < groupTextureCount; j++) { @@ -425,38 +384,11 @@ } // set the blend mode.. - //this.renderer.state.setBlendMode( group.blend ); + this.renderer.state.setBlendMode( group.blend ); gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); } -/* - // render the groups.. - for (i = 0; i < groupCount; ++i) - { - const group = groups[i]; - const groupTextureCount = group.textureCount; - for (let j = 0; j < groupTextureCount; j++) - { - currentTexture = group.textures[j]; - - // reset virtual ids.. - // lets do a quick check.. - if (rendererBoundTextures[group.ids[j]] !== currentTexture) - { - this.renderer.texture.bind(currentTexture, group.ids[j], true); - } - - // reset the virtualId.. - currentTexture._virtalBoundId = -1; - } - - // set the blend mode.. - this.renderer.state.setBlendMode(group.blend); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } -*/ // reset elements for the next flush this.currentIndex = 0; } @@ -466,16 +398,12 @@ */ start() { - // this.renderer._bindGLShader(this.shader); - this.renderer.shader.bind(this.shader, true); - this.renderer.shader.syncUniformGroup(this.shader.uniformGroup); + this.renderer.shader.bind(this.shader); if (settings.CAN_UPLOAD_SAME_BUFFER) { // bind buffer #0, we don't need others - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].bind(); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); } } diff --git a/src/core/sprites/webgl/generateMultiTextureShader.js b/src/core/sprites/webgl/generateMultiTextureShader.js index 1dabb7a..cab5255 100644 --- a/src/core/sprites/webgl/generateMultiTextureShader.js +++ b/src/core/sprites/webgl/generateMultiTextureShader.js @@ -1,5 +1,5 @@ -import { GLShader } from 'pixi-gl-core'; import Shader from '../../shader/Shader'; +import UniformGroup from '../../shader/UniformGroup'; import { PRECISION } from '../../const'; import { readFileSync } from 'fs'; import { join } from 'path'; @@ -20,25 +20,25 @@ export default function generateMultiTextureShader(gl, maxTextures) { - const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); - let fragmentSrc = fragTemplate; - - fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); - fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); - - const shaderold = new GLShader(gl, vertexSrc, fragmentSrc, PRECISION.DEFAULT); - const shader = Shader.from(vertexSrc, fragmentSrc);//, PRECISION.DEFAULT); - - const sampleValues = []; + const sampleValues = new Int32Array(maxTextures); for (let i = 0; i < maxTextures; i++) { sampleValues[i] = i; } - // shader.bind(); - // shader.uniforms.uSamplers = new Int32Array(sampleValues); - // shader.uniformGroup.static = true; + const uniforms = { + default:UniformGroup.from({uSamplers:sampleValues}, true), + } + + + const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); + let fragmentSrc = fragTemplate; + + fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); + fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); + + const shader = Shader.from(vertexSrc, fragmentSrc, uniforms);//, PRECISION.DEFAULT); return shader; } diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 57c4cc5..13417a1 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -450,6 +450,7 @@ } } +BaseTexture.fromFrame = BaseTexture.fromFrame; BaseTexture.fromImage = BaseTexture.from; BaseTexture.fromSVG = BaseTexture.from; BaseTexture.fromCanvas = BaseTexture.from; \ No newline at end of file diff --git a/src/core/textures/resources/VideoResource.js b/src/core/textures/resources/VideoResource.js index 7d525c0..3402f96 100644 --- a/src/core/textures/resources/VideoResource.js +++ b/src/core/textures/resources/VideoResource.js @@ -58,6 +58,7 @@ update() { + //TODO - slow down and base on the videos framerate this.resourceUpdated.emit(); } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 3fdd996..609da80 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -252,7 +252,7 @@ if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) { const args = [ - `\n %c %c %c Pixi.js ${VERSION} - ✰ ${type} ✰ %c %c http://www.pixijs.com/ %c %c ♥%c♥%c♥ \n\n`, + `\n %c %c %c Pixi.js ${VERSION} - ✰✰ ${type} ✰✰ %c %c http://www.pixijs.com/ %c %c ♥%c♥%c♥ \n\n`, 'background: #ff66a5; padding:5px 0;', 'background: #ff66a5; padding:5px 0;', 'color: #ff66a5; background: #030307; padding:5px 0;', diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index dd11031..ec46e24 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import RawMesh from './RawMesh'; -import Geometry from './geometry/Geometry'; +import Geometry from '../core/geometry/Geometry'; import * as core from '../core'; import { readFileSync } from 'fs'; import { join } from 'path'; diff --git a/src/mesh/geometry/Attribute.js b/src/mesh/geometry/Attribute.js deleted file mode 100644 index 83f92c2..0000000 --- a/src/mesh/geometry/Attribute.js +++ /dev/null @@ -1,58 +0,0 @@ -/* eslint-disable max-len */ - -/** - * holds the information for a single attribute structure required to render geometry. - * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} - * This can include anything from positions, uvs, normals, colors etc.. - * - * @class - * @memberof PIXI.mesh.Attribute - */ -class Attribute -{ - /** - * @param {string} buffer the id of the buffer that this attribute will look for - * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. - * @param {Boolean} [normalised=false] should the data be normalised. - * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available - * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) - * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) - */ - constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) - { - this.buffer = buffer; - this.size = size; - this.normalized = normalised; - this.type = type; - this.stride = stride; - this.start = start; - this.instance = instance; - } - - /** - * Destroys the Attribute. - */ - destroy() - { - this.buffer = null; - } - - /** - * Helper function that creates an Attribute based on the information provided - * - * @static - * @param {string} buffer the id of the buffer that this attribute will look for - * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 - * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) - * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) - * @param {Boolean} [normalised=false] should the data be normalised. - * - * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided - */ - static from(buffer, size, stride, start, normalised) - { - return new Attribute(buffer, size, stride, start, normalised); - } -} - -module.exports = Attribute; diff --git a/src/mesh/geometry/Buffer.js b/src/mesh/geometry/Buffer.js deleted file mode 100644 index 43d49bf..0000000 --- a/src/mesh/geometry/Buffer.js +++ /dev/null @@ -1,80 +0,0 @@ -let UID = 0; -/* eslint-disable max-len */ - -/** - * A wrapper for data so that it can be used and uploaded by webGL - * - * @class - * @memberof PIXI - */ -export default class Buffer -{ - /** - * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. - */ - constructor(data) - { - /** - * The data in the buffer, as a typed array - * - * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray - */ - this.data = data; - - /** - * A map of renderer IDs to webgl buffer - * - * @private - * @member {object} - */ - this._glBuffers = []; - - this._updateID = 0; - - this.index = false; - - this.static = true; - - this.id = UID++; - } - - // TODO could explore flagging only a partial upload? - /** - * flags this buffer as requiring an upload to the GPU - */ - update() - { - this._updateID++; - } - - /** - * Destroys the buffer - */ - destroy() - { - for (let i = 0; i < this._glBuffers.length; i++) - { - this._glBuffers[i].destroy(); - } - - this.data = null; - } - - /** - * Helper function that creates a buffer based on an array or TypedArray - * - * @static - * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. - * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. - */ - static from(data) - { - if (data instanceof Array) - { - data = new Float32Array(data); - } - - return new Buffer(data); - } -} - diff --git a/src/mesh/geometry/Geometry.js b/src/mesh/geometry/Geometry.js deleted file mode 100644 index c773eac..0000000 --- a/src/mesh/geometry/Geometry.js +++ /dev/null @@ -1,389 +0,0 @@ -import Attribute from './Attribute'; -import Buffer from './Buffer'; -import interleaveTypedArrays from '../../core/utils/interleaveTypedArrays'; -import getBufferType from '../../core/utils/getBufferType'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; -let UID = 0; - -/* eslint-disable object-shorthand */ -const map = { - Float32Array: Float32Array, - Uint32Array: Uint32Array, - Int32Array: Int32Array, - Uint16Array: Uint16Array, -}; - -/* eslint-disable max-len */ - -/** - * The Geometry represents a model. It consists of two components: - * GeometryStyle - The structure of the model such as the attributes layout - * GeometryData - the data of the model - this consits of buffers. - * - * This can include anything from positions, uvs, normals, colors etc.. - * - * Geometry can be defined without passing in a style or data if required (thats how I prefer!) - * - * ```js - * let geometry = new PIXI.mesh.Geometry(); - * - * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); - * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) - * geometry.addIndex([0,1,2,1,3,2]) - * - * ``` - * @class - * @memberof PIXI.mesh.Geometry - */ -export default class Geometry -{ - /** - * @param {array} buffers an array of buffers. optional. - * @param {object} attributes of the geometry, optional structure of the attributes layout - */ - constructor(buffers, attributes) - { - this.buffers = buffers || []; - - this.indexBuffer = null; - - this.attributes = attributes || {}; - - /** - * A map of renderer IDs to webgl VAOs - * - * @private - * @type {Array} - */ - this.glVertexArrayObjects = []; - - this.id = UID++; - } - - /** - * - * Adds an attribute to the geometry - * - * @param {String} id - the name of the attribute (matching up to a shader) - * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. - * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 - * @param {Boolean} [normalised=false] should the data be normalised. - * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available - * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) - * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) - * - * @return {PIXI.mesh.Geometry} returns self, useful for chaining. - */ - addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) - { - if (!buffer) - { - throw new Error('You must pass a buffer when creating an attribute'); - } - - // check if this is a buffer! - if (!buffer.data) - { - // its an array! - if (buffer instanceof Array) - { - buffer = new Float32Array(buffer); - } - - buffer = new Buffer(buffer); - } - - const ids = id.split('|'); - - if (ids.length > 1) - { - for (let i = 0; i < ids.length; i++) - { - this.addAttribute(ids[i], buffer, size, normalised, type); - } - - return this; - } - - let bufferIndex = this.buffers.indexOf(buffer); - - if (bufferIndex === -1) - { - this.buffers.push(buffer); - bufferIndex = this.buffers.length - 1; - } - - this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); - - return this; - } - - /** - * returns the requested attribute - * - * @param {String} id the name of the attribute required - * @return {PIXI.mesh.Attribute} the attribute requested. - */ - getAttribute(id) - { - return this.buffers[this.attributes[id].buffer]; - } - - /** - * - * Adds an index buffer to the geometry - * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. - * - * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. - * @return {PIXI.mesh.Geometry} returns self, useful for chaining. - */ - addIndex(buffer) - { - if (!buffer.data) - { - // its an array! - if (buffer instanceof Array) - { - buffer = new Uint16Array(buffer); - } - - buffer = new Buffer(buffer); - } - - buffer.index = true; - this.indexBuffer = buffer; - - if (this.buffers.indexOf(buffer) === -1) - { - this.buffers.push(buffer); - } - - return this; - } - - /** - * returns the index buffer - * - * @return {PIXI.mesh.Buffer} the index buffer. - */ - getIndex() - { - return this.indexBuffer; - } - - /** - * this function modifies the structure so that all current attributes become interleaved into a single buffer - * This can be useful if your model remains static as it offers a little performance boost - * - * @return {PIXI.mesh.Geometry} returns self, useful for chaining. - */ - interleave() - { - // a simple check to see if buffers are already interleaved.. - if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; - - // assume already that no buffers are interleaved - const arrays = []; - const sizes = []; - const interleavedBuffer = new Buffer(); - let i; - - for (i in this.attributes) - { - const attribute = this.attributes[i]; - - const buffer = this.buffers[attribute.buffer]; - - arrays.push(buffer.data); - - sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); - - attribute.buffer = 0; - } - - interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); - - for (i = 0; i < this.buffers.length; i++) - { - if (this.buffers[i] !== this.indexBuffer) - { - this.buffers[i].destroy(); - } - } - - this.buffers = [interleavedBuffer]; - - if (this.indexBuffer) - { - this.buffers.push(this.indexBuffer); - } - - return this; - } - - /** - * Destroys the geometry. - */ - destroy() - { - for (let i = 0; i < this.glVertexArrayObjects.length; i++) - { - this.glVertexArrayObjects[i].destroy(); - } - - this.glVertexArrayObjects = null; - - for (let i = 0; i < this.buffers.length; i++) - { - this.buffers[i].destroy(); - } - - this.buffers = null; - this.indexBuffer.destroy(); - - this.attributes = null; - } - - /** - * returns a clone of the geometry - * - * @returns {PIXI.mesh.Geometry} a new clone of this geometry - */ - clone() - { - const geometry = new Geometry(); - - for (let i = 0; i < this.buffers.length; i++) - { - geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); - } - - for (const i in this.attributes) - { - const attrib = this.attributes[i]; - - geometry.attributes[i] = new Attribute( - attrib.buffer, - attrib.size, - attrib.normalized, - attrib.type, - attrib.stride, - attrib.start, - attrib.instance - ); - } - - if (this.indexBuffer) - { - geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; - geometry.indexBuffer.index = true; - } - - return geometry; - } - - /** - * merges an array of geometries into a new single one - * geometry attribute styles must match for this operation to work - * - * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge - * @returns {PIXI.mesh.Geometry} shiney new geometry - */ - static merge(geometries) - { - // todo add a geometry check! - // also a size check.. cant be too big!] - - const geometryOut = new Geometry(); - - const arrays = []; - const sizes = []; - const offsets = []; - - let geometry; - - // pass one.. get sizes.. - for (let i = 0; i < geometries.length; i++) - { - geometry = geometries[i]; - - for (let j = 0; j < geometry.buffers.length; j++) - { - sizes[j] = sizes[j] || 0; - sizes[j] += geometry.buffers[j].data.length; - offsets[j] = 0; - } - } - - // build the correct size arrays.. - for (let i = 0; i < geometry.buffers.length; i++) - { - // TODO types! - arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); - geometryOut.buffers[i] = new Buffer(arrays[i]); - } - - // pass to set data.. - for (let i = 0; i < geometries.length; i++) - { - geometry = geometries[i]; - - for (let j = 0; j < geometry.buffers.length; j++) - { - arrays[j].set(geometry.buffers[j].data, offsets[j]); - offsets[j] += geometry.buffers[j].data.length; - } - } - - geometryOut.attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; - geometryOut.indexBuffer.index = true; - - let offset = 0; - let stride = 0; - let offset2 = 0; - let bufferIndexToCount = 0; - - // get a buffer - for (let i = 0; i < geometry.buffers.length; i++) - { - if (geometry.buffers[i] !== geometry.indexBuffer) - { - bufferIndexToCount = i; - break; - } - } - - // figure out the stride of one buffer.. - for (const i in geometry.attributes) - { - const attribute = geometry.attributes[i]; - - if ((attribute.buffer | 0) === bufferIndexToCount) - { - stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); - } - } - - // time to off set all indexes.. - for (let i = 0; i < geometries.length; i++) - { - const indexBufferData = geometries[i].indexBuffer.data; - - for (let j = 0; j < indexBufferData.length; j++) - { - geometryOut.indexBuffer.data[j + offset2] += offset; - } - - offset += geometry.buffers[bufferIndexToCount].data.length / (stride); - offset2 += indexBufferData.length; - } - } - - return geometryOut; - } -} diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index ac37001..c5d09cd 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -142,6 +142,8 @@ { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); + + const isArray = uniformData.name.match(/\[.*?\]/, ''); const type = shaderUtils.mapType(gl, uniformData.type); // if (!name.match(maskRegex)) @@ -150,6 +152,7 @@ uniforms[name] = { type: type, size: uniformData.size, + isArray:isArray, value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index ad4fcfc..2f8dc56 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -40,54 +40,41 @@ // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniformGroup.uniforms[i]; - - if (!uniform) // bad as we need check wher ethe uniforms are.. + if (this.uniformGroup.uniforms[i] instanceof Array) { - //this.uniformGroup.uniforms[i] = program.uniformData[i].value; - } - else if (uniform instanceof Array) - { - this.uniformGroup.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(this.uniformGroup.uniforms[i]); } } } + checkUniformExists(name, group) + { + if(group.uniforms[name]) + { + return true; + } + + for (const i in group.uniforms) + { + const uniform = group.uniforms[i]; + + if(uniform.group) + { + if( this.checkUniformExists(name, uniform) ) + { + return true; + } + } + } + + return false; + } + get uniforms() { return this.uniformGroup.uniforms; } -/* -var offsetBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW); - -var colorBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); - -var instanceCount = 4; -var ext = gl.getExtension("ANGLE_instanced_arrays"); // Vendor prefixes may apply! - -// Bind the rest of the vertex attributes normally -bindMeshArrays(gl); - -// Bind the instance position data -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.enableVertexAttribArray(offsetLocation); -gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 12, 0); -ext.vertexAttribDivisorANGLE(offsetLocation, 1); // This makes it instanced! - -// Bind the instance color data -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.enableVertexAttribArray(colorLocation); -gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 16, 0); -ext.vertexAttribDivisorANGLE(colorLocation, 1); // This makes it instanced! - -// Draw the instanced meshes -ext.drawElementsInstancedANGLE(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount); -*/ /** * A short hand function to create a shader based of a vertex and fragment shader * diff --git a/src/core/shader/UniformGroup.js b/src/core/shader/UniformGroup.js index 8768226..e2ed22a 100644 --- a/src/core/shader/UniformGroup.js +++ b/src/core/shader/UniformGroup.js @@ -1,5 +1,3 @@ -import Program from './Program'; -import generateUniformsSync from './generateUniformsSync'; let UID = 0; diff --git a/src/core/shader/generateUniformsSync.js b/src/core/shader/generateUniformsSync.js index b7241ef..8a7cf55 100644 --- a/src/core/shader/generateUniformsSync.js +++ b/src/core/shader/generateUniformsSync.js @@ -1,153 +1,190 @@ + + +//cv = CachedValue +//v = value +//ud = uniformData +//uv = uniformValue +//l = loaction const GLSL_TO_SINGLE_SETTERS_CACHED = { - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, + float:` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; + vec2:` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', + vec3:` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + gl.uniform3f(location, v[0], v[1], v[2]) + }`, - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)' + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', }; const GLSL_TO_ARRAY_SETTERS = { - float: `gl.uniform1fv(location, value)`, + float: `gl.uniform1fv(location, v)`, - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)' + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', }; -export default function generateUniformsSync(uniformData) + +export default function generateUniformsSync(group, uniformData) { - let textureCount = 1; - let func = `var value = null; - var cacheValue = null + let textureCount = 0; + let func = `var v = null; + var cv = null var gl = renderer.gl`; - for (const i in uniformData) + for (const i in group.uniforms) { const data = uniformData[i]; - // console.log(i, data.size); + + if(!data) + { + if(group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + ` + } + + continue; + } // TODO && uniformData[i].value !== 0 <-- do we still need this? if (data.type === 'float' && data.size === 1) { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; } - else if (data.type === 'sampler2D' && data.size === 1) + else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) { - func += `\nif (uniformValues.${i}.baseTexture) -{ - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount++}, false); + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - } -} -else -{ - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n` - uniformValues.${i}.bind(); -}`; + textureCount++; } else if (data.type === 'mat3' && data.size === 1) { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; + if(group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } } else if (data.type === 'vec2' && data.size === 1) { // TODO - do we need both here? // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; + if(group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + }\n`; + } } else { const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); + const template = templateType[data.type].replace('location', `ud.${i}.location`); - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; } } - //console.log(' --------------- ') - // console.log(func); + // console.log(' --------------- ') + // console.log(func); - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func } diff --git a/src/core/shader/generateUniformsSync2.js b/src/core/shader/generateUniformsSync2.js deleted file mode 100644 index 8a0fd6b..0000000 --- a/src/core/shader/generateUniformsSync2.js +++ /dev/null @@ -1,190 +0,0 @@ -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, - - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; - - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', - - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', - - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)', - sampler2DArray: 'gl.uniform1i(location, value)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, value)`, - - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', - - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', - - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', - - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)', - sampler2DArray: 'gl.uniform1iv(location, value)', -}; - -export default function generateUniformsSync2(group, uniformData) -{ - let textureCount = 0; - let func = `var value = null; - var cacheValue = null - var gl = renderer.gl`; - - - for (const i in group.uniforms) - { - const data = uniformData[i]; - //group.uniforms[i]; - // console.log(i, data); - if(!data) - { - if(group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uniformValues.${i}); - ` - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; - } - else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1) - { - func += `\nif (uniformValues.${i}.baseTexture) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } - - /* - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount}, false); - - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - }*/ -} -else if(uniformValues.${i}._new) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } -} -else -{ - // console.log("binding..") - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount}); - - uniformValues.${i}.bind(); -}`; - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; - -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); - - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; - } - } - - console.log(' --------------- ') - console.log(func); - - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 5b486ba..57b4b3c 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -7,6 +7,10 @@ import Buffer from './BatchBuffer'; import settings from '../../settings'; import bitTwiddle from 'bit-twiddle'; +import Geometry from '../../geometry/Geometry'; +//TODO rename this +import Buffer_GEOM from '../../geometry/Buffer'; + let TICK = 0; let TEXTURE_TICK = 0; @@ -65,6 +69,7 @@ * @member {Uint16Array} */ this.indices = createIndicesForQuads(this.size); + this.indexBuffer = new Buffer_GEOM(this.indices, true, true); /** * The default shaders that is used if a sprite doesn't have a more specific one. @@ -115,50 +120,27 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } - const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); - const sampleValues = new Int32Array(this.MAX_TEXTURES); - - for (let i = 0; i < this.MAX_TEXTURES; i++) - { - sampleValues[i] = i; - } - - shader.uniformGroup.add('default', {uSamplers:sampleValues}, true);//this.renderer.globalUniforms; - shader.uniforms.globals = this.renderer.globalUniforms; - - this.indexBuffer = GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // generate generateMultiTextureProgram, may be a better move? + this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. - - this.renderer.geometry.bindVao(null); - for (let i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + var buffer = new Buffer_GEOM(null, false); + /* eslint-disable max-len */ - - var attributeData = shader.program.attributeData; - // build the vao object that will render.. - this.vaos[i] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[i].addAttribute(this.vertexBuffers[i], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[i] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[i] = buffer; } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; - - this.boundTextures = new Array(this.MAX_TEXTURES); } /** @@ -221,9 +203,7 @@ const float32View = buffer.float32View; const uint32View = buffer.uint32View; - const boundTextures = this.boundTextures; - const rendererBoundTextures = this.renderer.boundTextures; - const touch = 0//this.renderer.textureGC.count; + const touch = 0;//this.renderer.textureGC.count; let index = 0; let nextTexture; @@ -244,13 +224,6 @@ let i; - // copy textures.. - for (i = 0; i < MAX_TEXTURES; ++i) - { - boundTextures[i] = rendererBoundTextures[i]; - boundTextures[i]._virtalBoundId = i; - } - for (i = 0; i < this.currentIndex; ++i) { // upload the sprite elemetns... @@ -371,37 +344,32 @@ if (this.vaoMax <= this.vertexCount) { this.vaoMax++; - this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + + let buffer = new Buffer_GEOM(null, false); /* eslint-disable max-len */ - - var attributeData = this.shader.program.attributeData; - - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[this.vertexCount].addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[this.vertexCount] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[this.vertexCount] = buffer; } - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); this.vertexCount++; } else { // lets use the faster option, always use buffer number 0 - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, true); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + + this.renderer.geometry.updateBuffers(); } /// render the groups.. @@ -409,15 +377,6 @@ var group = groups[i]; var groupTextureCount = group.textureCount; - //shader = this.shaders[groupTextureCount-1]; - - //if(!shader) - // { - // shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - //} - - /// this.renderer.shader.bind(shader); for (var j = 0; j < groupTextureCount; j++) { @@ -425,38 +384,11 @@ } // set the blend mode.. - //this.renderer.state.setBlendMode( group.blend ); + this.renderer.state.setBlendMode( group.blend ); gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); } -/* - // render the groups.. - for (i = 0; i < groupCount; ++i) - { - const group = groups[i]; - const groupTextureCount = group.textureCount; - for (let j = 0; j < groupTextureCount; j++) - { - currentTexture = group.textures[j]; - - // reset virtual ids.. - // lets do a quick check.. - if (rendererBoundTextures[group.ids[j]] !== currentTexture) - { - this.renderer.texture.bind(currentTexture, group.ids[j], true); - } - - // reset the virtualId.. - currentTexture._virtalBoundId = -1; - } - - // set the blend mode.. - this.renderer.state.setBlendMode(group.blend); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } -*/ // reset elements for the next flush this.currentIndex = 0; } @@ -466,16 +398,12 @@ */ start() { - // this.renderer._bindGLShader(this.shader); - this.renderer.shader.bind(this.shader, true); - this.renderer.shader.syncUniformGroup(this.shader.uniformGroup); + this.renderer.shader.bind(this.shader); if (settings.CAN_UPLOAD_SAME_BUFFER) { // bind buffer #0, we don't need others - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].bind(); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); } } diff --git a/src/core/sprites/webgl/generateMultiTextureShader.js b/src/core/sprites/webgl/generateMultiTextureShader.js index 1dabb7a..cab5255 100644 --- a/src/core/sprites/webgl/generateMultiTextureShader.js +++ b/src/core/sprites/webgl/generateMultiTextureShader.js @@ -1,5 +1,5 @@ -import { GLShader } from 'pixi-gl-core'; import Shader from '../../shader/Shader'; +import UniformGroup from '../../shader/UniformGroup'; import { PRECISION } from '../../const'; import { readFileSync } from 'fs'; import { join } from 'path'; @@ -20,25 +20,25 @@ export default function generateMultiTextureShader(gl, maxTextures) { - const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); - let fragmentSrc = fragTemplate; - - fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); - fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); - - const shaderold = new GLShader(gl, vertexSrc, fragmentSrc, PRECISION.DEFAULT); - const shader = Shader.from(vertexSrc, fragmentSrc);//, PRECISION.DEFAULT); - - const sampleValues = []; + const sampleValues = new Int32Array(maxTextures); for (let i = 0; i < maxTextures; i++) { sampleValues[i] = i; } - // shader.bind(); - // shader.uniforms.uSamplers = new Int32Array(sampleValues); - // shader.uniformGroup.static = true; + const uniforms = { + default:UniformGroup.from({uSamplers:sampleValues}, true), + } + + + const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); + let fragmentSrc = fragTemplate; + + fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); + fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); + + const shader = Shader.from(vertexSrc, fragmentSrc, uniforms);//, PRECISION.DEFAULT); return shader; } diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 57c4cc5..13417a1 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -450,6 +450,7 @@ } } +BaseTexture.fromFrame = BaseTexture.fromFrame; BaseTexture.fromImage = BaseTexture.from; BaseTexture.fromSVG = BaseTexture.from; BaseTexture.fromCanvas = BaseTexture.from; \ No newline at end of file diff --git a/src/core/textures/resources/VideoResource.js b/src/core/textures/resources/VideoResource.js index 7d525c0..3402f96 100644 --- a/src/core/textures/resources/VideoResource.js +++ b/src/core/textures/resources/VideoResource.js @@ -58,6 +58,7 @@ update() { + //TODO - slow down and base on the videos framerate this.resourceUpdated.emit(); } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 3fdd996..609da80 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -252,7 +252,7 @@ if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) { const args = [ - `\n %c %c %c Pixi.js ${VERSION} - ✰ ${type} ✰ %c %c http://www.pixijs.com/ %c %c ♥%c♥%c♥ \n\n`, + `\n %c %c %c Pixi.js ${VERSION} - ✰✰ ${type} ✰✰ %c %c http://www.pixijs.com/ %c %c ♥%c♥%c♥ \n\n`, 'background: #ff66a5; padding:5px 0;', 'background: #ff66a5; padding:5px 0;', 'color: #ff66a5; background: #030307; padding:5px 0;', diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index dd11031..ec46e24 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import RawMesh from './RawMesh'; -import Geometry from './geometry/Geometry'; +import Geometry from '../core/geometry/Geometry'; import * as core from '../core'; import { readFileSync } from 'fs'; import { join } from 'path'; diff --git a/src/mesh/geometry/Attribute.js b/src/mesh/geometry/Attribute.js deleted file mode 100644 index 83f92c2..0000000 --- a/src/mesh/geometry/Attribute.js +++ /dev/null @@ -1,58 +0,0 @@ -/* eslint-disable max-len */ - -/** - * holds the information for a single attribute structure required to render geometry. - * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} - * This can include anything from positions, uvs, normals, colors etc.. - * - * @class - * @memberof PIXI.mesh.Attribute - */ -class Attribute -{ - /** - * @param {string} buffer the id of the buffer that this attribute will look for - * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. - * @param {Boolean} [normalised=false] should the data be normalised. - * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available - * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) - * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) - */ - constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) - { - this.buffer = buffer; - this.size = size; - this.normalized = normalised; - this.type = type; - this.stride = stride; - this.start = start; - this.instance = instance; - } - - /** - * Destroys the Attribute. - */ - destroy() - { - this.buffer = null; - } - - /** - * Helper function that creates an Attribute based on the information provided - * - * @static - * @param {string} buffer the id of the buffer that this attribute will look for - * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 - * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) - * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) - * @param {Boolean} [normalised=false] should the data be normalised. - * - * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided - */ - static from(buffer, size, stride, start, normalised) - { - return new Attribute(buffer, size, stride, start, normalised); - } -} - -module.exports = Attribute; diff --git a/src/mesh/geometry/Buffer.js b/src/mesh/geometry/Buffer.js deleted file mode 100644 index 43d49bf..0000000 --- a/src/mesh/geometry/Buffer.js +++ /dev/null @@ -1,80 +0,0 @@ -let UID = 0; -/* eslint-disable max-len */ - -/** - * A wrapper for data so that it can be used and uploaded by webGL - * - * @class - * @memberof PIXI - */ -export default class Buffer -{ - /** - * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. - */ - constructor(data) - { - /** - * The data in the buffer, as a typed array - * - * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray - */ - this.data = data; - - /** - * A map of renderer IDs to webgl buffer - * - * @private - * @member {object} - */ - this._glBuffers = []; - - this._updateID = 0; - - this.index = false; - - this.static = true; - - this.id = UID++; - } - - // TODO could explore flagging only a partial upload? - /** - * flags this buffer as requiring an upload to the GPU - */ - update() - { - this._updateID++; - } - - /** - * Destroys the buffer - */ - destroy() - { - for (let i = 0; i < this._glBuffers.length; i++) - { - this._glBuffers[i].destroy(); - } - - this.data = null; - } - - /** - * Helper function that creates a buffer based on an array or TypedArray - * - * @static - * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. - * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. - */ - static from(data) - { - if (data instanceof Array) - { - data = new Float32Array(data); - } - - return new Buffer(data); - } -} - diff --git a/src/mesh/geometry/Geometry.js b/src/mesh/geometry/Geometry.js deleted file mode 100644 index c773eac..0000000 --- a/src/mesh/geometry/Geometry.js +++ /dev/null @@ -1,389 +0,0 @@ -import Attribute from './Attribute'; -import Buffer from './Buffer'; -import interleaveTypedArrays from '../../core/utils/interleaveTypedArrays'; -import getBufferType from '../../core/utils/getBufferType'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; -let UID = 0; - -/* eslint-disable object-shorthand */ -const map = { - Float32Array: Float32Array, - Uint32Array: Uint32Array, - Int32Array: Int32Array, - Uint16Array: Uint16Array, -}; - -/* eslint-disable max-len */ - -/** - * The Geometry represents a model. It consists of two components: - * GeometryStyle - The structure of the model such as the attributes layout - * GeometryData - the data of the model - this consits of buffers. - * - * This can include anything from positions, uvs, normals, colors etc.. - * - * Geometry can be defined without passing in a style or data if required (thats how I prefer!) - * - * ```js - * let geometry = new PIXI.mesh.Geometry(); - * - * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); - * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) - * geometry.addIndex([0,1,2,1,3,2]) - * - * ``` - * @class - * @memberof PIXI.mesh.Geometry - */ -export default class Geometry -{ - /** - * @param {array} buffers an array of buffers. optional. - * @param {object} attributes of the geometry, optional structure of the attributes layout - */ - constructor(buffers, attributes) - { - this.buffers = buffers || []; - - this.indexBuffer = null; - - this.attributes = attributes || {}; - - /** - * A map of renderer IDs to webgl VAOs - * - * @private - * @type {Array} - */ - this.glVertexArrayObjects = []; - - this.id = UID++; - } - - /** - * - * Adds an attribute to the geometry - * - * @param {String} id - the name of the attribute (matching up to a shader) - * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. - * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 - * @param {Boolean} [normalised=false] should the data be normalised. - * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available - * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) - * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) - * - * @return {PIXI.mesh.Geometry} returns self, useful for chaining. - */ - addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) - { - if (!buffer) - { - throw new Error('You must pass a buffer when creating an attribute'); - } - - // check if this is a buffer! - if (!buffer.data) - { - // its an array! - if (buffer instanceof Array) - { - buffer = new Float32Array(buffer); - } - - buffer = new Buffer(buffer); - } - - const ids = id.split('|'); - - if (ids.length > 1) - { - for (let i = 0; i < ids.length; i++) - { - this.addAttribute(ids[i], buffer, size, normalised, type); - } - - return this; - } - - let bufferIndex = this.buffers.indexOf(buffer); - - if (bufferIndex === -1) - { - this.buffers.push(buffer); - bufferIndex = this.buffers.length - 1; - } - - this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); - - return this; - } - - /** - * returns the requested attribute - * - * @param {String} id the name of the attribute required - * @return {PIXI.mesh.Attribute} the attribute requested. - */ - getAttribute(id) - { - return this.buffers[this.attributes[id].buffer]; - } - - /** - * - * Adds an index buffer to the geometry - * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. - * - * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. - * @return {PIXI.mesh.Geometry} returns self, useful for chaining. - */ - addIndex(buffer) - { - if (!buffer.data) - { - // its an array! - if (buffer instanceof Array) - { - buffer = new Uint16Array(buffer); - } - - buffer = new Buffer(buffer); - } - - buffer.index = true; - this.indexBuffer = buffer; - - if (this.buffers.indexOf(buffer) === -1) - { - this.buffers.push(buffer); - } - - return this; - } - - /** - * returns the index buffer - * - * @return {PIXI.mesh.Buffer} the index buffer. - */ - getIndex() - { - return this.indexBuffer; - } - - /** - * this function modifies the structure so that all current attributes become interleaved into a single buffer - * This can be useful if your model remains static as it offers a little performance boost - * - * @return {PIXI.mesh.Geometry} returns self, useful for chaining. - */ - interleave() - { - // a simple check to see if buffers are already interleaved.. - if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; - - // assume already that no buffers are interleaved - const arrays = []; - const sizes = []; - const interleavedBuffer = new Buffer(); - let i; - - for (i in this.attributes) - { - const attribute = this.attributes[i]; - - const buffer = this.buffers[attribute.buffer]; - - arrays.push(buffer.data); - - sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); - - attribute.buffer = 0; - } - - interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); - - for (i = 0; i < this.buffers.length; i++) - { - if (this.buffers[i] !== this.indexBuffer) - { - this.buffers[i].destroy(); - } - } - - this.buffers = [interleavedBuffer]; - - if (this.indexBuffer) - { - this.buffers.push(this.indexBuffer); - } - - return this; - } - - /** - * Destroys the geometry. - */ - destroy() - { - for (let i = 0; i < this.glVertexArrayObjects.length; i++) - { - this.glVertexArrayObjects[i].destroy(); - } - - this.glVertexArrayObjects = null; - - for (let i = 0; i < this.buffers.length; i++) - { - this.buffers[i].destroy(); - } - - this.buffers = null; - this.indexBuffer.destroy(); - - this.attributes = null; - } - - /** - * returns a clone of the geometry - * - * @returns {PIXI.mesh.Geometry} a new clone of this geometry - */ - clone() - { - const geometry = new Geometry(); - - for (let i = 0; i < this.buffers.length; i++) - { - geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); - } - - for (const i in this.attributes) - { - const attrib = this.attributes[i]; - - geometry.attributes[i] = new Attribute( - attrib.buffer, - attrib.size, - attrib.normalized, - attrib.type, - attrib.stride, - attrib.start, - attrib.instance - ); - } - - if (this.indexBuffer) - { - geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; - geometry.indexBuffer.index = true; - } - - return geometry; - } - - /** - * merges an array of geometries into a new single one - * geometry attribute styles must match for this operation to work - * - * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge - * @returns {PIXI.mesh.Geometry} shiney new geometry - */ - static merge(geometries) - { - // todo add a geometry check! - // also a size check.. cant be too big!] - - const geometryOut = new Geometry(); - - const arrays = []; - const sizes = []; - const offsets = []; - - let geometry; - - // pass one.. get sizes.. - for (let i = 0; i < geometries.length; i++) - { - geometry = geometries[i]; - - for (let j = 0; j < geometry.buffers.length; j++) - { - sizes[j] = sizes[j] || 0; - sizes[j] += geometry.buffers[j].data.length; - offsets[j] = 0; - } - } - - // build the correct size arrays.. - for (let i = 0; i < geometry.buffers.length; i++) - { - // TODO types! - arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); - geometryOut.buffers[i] = new Buffer(arrays[i]); - } - - // pass to set data.. - for (let i = 0; i < geometries.length; i++) - { - geometry = geometries[i]; - - for (let j = 0; j < geometry.buffers.length; j++) - { - arrays[j].set(geometry.buffers[j].data, offsets[j]); - offsets[j] += geometry.buffers[j].data.length; - } - } - - geometryOut.attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; - geometryOut.indexBuffer.index = true; - - let offset = 0; - let stride = 0; - let offset2 = 0; - let bufferIndexToCount = 0; - - // get a buffer - for (let i = 0; i < geometry.buffers.length; i++) - { - if (geometry.buffers[i] !== geometry.indexBuffer) - { - bufferIndexToCount = i; - break; - } - } - - // figure out the stride of one buffer.. - for (const i in geometry.attributes) - { - const attribute = geometry.attributes[i]; - - if ((attribute.buffer | 0) === bufferIndexToCount) - { - stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); - } - } - - // time to off set all indexes.. - for (let i = 0; i < geometries.length; i++) - { - const indexBufferData = geometries[i].indexBuffer.data; - - for (let j = 0; j < indexBufferData.length; j++) - { - geometryOut.indexBuffer.data[j + offset2] += offset; - } - - offset += geometry.buffers[bufferIndexToCount].data.length / (stride); - offset2 += indexBufferData.length; - } - } - - return geometryOut; - } -} diff --git a/src/mesh/index.js b/src/mesh/index.js index 9094f68..e4a24cd 100644 --- a/src/mesh/index.js +++ b/src/mesh/index.js @@ -5,9 +5,9 @@ export { default as RawMesh } from './RawMesh'; export { default as MeshRenderer } from './webgl/MeshRenderer'; export { default as CanvasMeshRenderer } from './canvas/CanvasMeshRenderer'; -export { default as Attribute } from './geometry/Attribute'; -export { default as Buffer } from './geometry/Buffer'; -export { default as Geometry } from './geometry/Geometry'; +export { default as Attribute } from '../core/geometry/Attribute'; +export { default as Buffer } from '../core/geometry/Buffer'; +export { default as Geometry } from '../core/geometry/Geometry'; export { default as Plane } from './Plane'; export { default as NineSlicePlane } from './NineSlicePlane'; export { default as Rope } from './Rope'; diff --git a/package.json b/package.json index 2466363..829456f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.5.0", + "version": "5.0.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ diff --git a/src/core/geometry/Buffer.js b/src/core/geometry/Buffer.js index 782d2c2..15c656b 100644 --- a/src/core/geometry/Buffer.js +++ b/src/core/geometry/Buffer.js @@ -12,7 +12,7 @@ /** * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. */ - constructor(data) + constructor(data, _static = true, index = false) { /** * The data in the buffer, as a typed array @@ -31,9 +31,9 @@ this._updateID = 0; - this.index = false; + this.index = index; - this.static = true; + this.static = _static; this.id = UID++; } diff --git a/src/core/geometry/Geometry.js b/src/core/geometry/Geometry.js index 83f0118..c8ed986 100644 --- a/src/core/geometry/Geometry.js +++ b/src/core/geometry/Geometry.js @@ -59,6 +59,10 @@ this.glVertexArrayObjects = {}; this.id = UID++; + + this.instanced = false; + + this._size = null; } /** @@ -116,6 +120,9 @@ this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); + // assuming that if there is instanced data then this will be drawn with instancing! + this.instanced = this.instanced || instance; + this._si return this; } @@ -222,6 +229,20 @@ return this; } + getSize() + { + for (var i in this.attributes) + { + const attribute = this.attributes[i]; + const buffer = this.buffers[attribute.buffer]; + + return buffer.data.length / (( attribute.stride/4 ) || attribute.size); + } + + return 0; + }; + + /** * Destroys the geometry. */ diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index b9f192d..18c0a9d 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -42,7 +42,7 @@ { super(); - sayHello(system); + // Support for constructor(system, screenWidth, screenHeight, options) if (typeof options === 'number') diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index a41e1e5..dc83e69 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,4 +1,5 @@ import SystemRenderer from '../SystemRenderer'; +import { sayHello } from '../../utils'; import MaskSystem from './systems/MaskSystem'; import StencilSystem from './systems/StencilSystem'; import FilterSystem from './systems/FilterSystem'; @@ -93,8 +94,8 @@ this._backgroundColorRgba[3] = this.transparent ? 0 : 1; this.globalUniforms = new UniformGroup({ - projectionMatrix:new Matrix() - }, false) + projectionMatrix:new Matrix(), + }, true) this.addSystem(MaskSystem, 'mask') .addSystem(ContextSystem, 'context') @@ -137,6 +138,8 @@ this.renderingToScreen = true; this._initContext(); + + sayHello(this.context.webGLVersion === 2 ? 'WebGL' : 'WebGL 2'); } addSystem(_class, name) diff --git a/src/core/renderers/webgl/systems/ContextSystem.js b/src/core/renderers/webgl/systems/ContextSystem.js index 8849245..16b3822 100644 --- a/src/core/renderers/webgl/systems/ContextSystem.js +++ b/src/core/renderers/webgl/systems/ContextSystem.js @@ -18,6 +18,8 @@ { super(renderer); + this.webGLversion = 1; + this.handleContextLost = this.handleContextLost.bind(this); this.handleContextRestored = this.handleContextRestored.bind(this); @@ -75,13 +77,24 @@ */ createContext(canvas, options) { - var gl = canvas.getContext('webgl', options) || - canvas.getContext('experimental-webgl', options); + var gl = canvas.getContext('webgl2', options); - if (!gl) + if(gl) { - // fail, not able to get a context - throw new Error('This browser does not support webGL. Try using the canvas renderer'); + this.webGLversion = 2; + } + else + { + this.webGLversion = 1; + + 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; diff --git a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js index 837ba94..3f95a06 100644 --- a/src/core/renderers/webgl/systems/geometry/GeometrySystem.js +++ b/src/core/renderers/webgl/systems/geometry/GeometrySystem.js @@ -22,7 +22,11 @@ { super(renderer); + this._activeGeometry = null; this._activeVao = null; + + this.hasVao = true; + this.hasInstance = true; } /** @@ -32,8 +36,44 @@ */ contextChange() { - this.gl = this.renderer.gl; + const gl = this.gl = this.renderer.gl; this.CONTEXT_UID = this.renderer.CONTEXT_UID; + + // webgl2 + if(!gl.createVertexArray) + { + // webgl 1! + const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object'); + + if(nativeVaoExtension) + { + gl.createVertexArray = nativeVaoExtension.createVertexArrayOES; + gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES; + gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES; + } + else + { + this.hasVao = false; + gl.createVertexArray = ()=>{return {}} + gl.bindVertexArray = ()=>{} + gl.deleteVertexArray = ()=>{} + } + } + + if(!gl.vertexAttribDivisor) + { + const instanceExt = gl.getExtension("ANGLE_instanced_arrays"); + + if(instanceExt) + { + gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE; + gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE; + gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE; + } + } + } /** @@ -41,15 +81,54 @@ * @private * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind */ - bind(geometry, glShader) + bind(geometry, shader) { - const vao = geometry.glVertexArrayObjects[this.CONTEXT_UID] || this.initGeometryVao(geometry, glShader); + shader = shader || this.renderer.shader.shader; - this.bindVao(vao); + const gl = this.gl; + + + // not sure the best way to address this.. + // currently different shaders require different VAOs for the same geometry + // Still mulling over the best way to solve this one.. + // will likely need to modify the shader attribute locations at run time! + let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID]; + + if(!vaos) + { + vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {}; + } + + const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program); + + this._activeGeometry = geometry; + + if(this._activeVao !== vao) + { + this._activeVao = vao; + + if(this.hasVao) + { + gl.bindVertexArray(vao); + } + else + { + this.activateVao(geometry, shader.program); + } + } // TODO - optimise later! // don't need to loop through if nothing changed! // maybe look to add an 'autoupdate' to geometry? + this.updateBuffers(); + } + + + updateBuffers() + { + const geometry = this._activeGeometry; + const gl = this.gl; + for (let i = 0; i < geometry.buffers.length; i++) { const buffer = geometry.buffers[i]; @@ -59,8 +138,38 @@ if (buffer._updateID !== glBuffer._updateID) { glBuffer._updateID = buffer._updateID; - // TODO - partial upload?? - glBuffer.upload(buffer.data, 0); + + // TODO can cache this on buffer! maybe added a getter / setter? + const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; + const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + + gl.bindBuffer(type, glBuffer.buffer); + + if(glBuffer.byteLength >= buffer.data.byteLength) + { + // offset is always zero for now! + gl.bufferSubData(type, 0, buffer.data); + } + else + { + gl.bufferData(type, buffer.data, drawType); + } + + } + } + } + + checkCompatability(geometry, program) + { + // geometry must have at least all the attributes that the shader requires. + const geometryAttributes = geometry.attributes; + const shaderAttributes = program.attributeData; + + for (const j in shaderAttributes) + { + if(!geometryAttributes[j]) + { + throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute'); } } } @@ -71,14 +180,12 @@ * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for * @return {PIXI.VertexArrayObject} Returns a fresh vao. */ - initGeometryVao(geometry, glShader) + initGeometryVao(geometry, program) { + this.checkCompatability(geometry, program); + const gl = this.gl; - - this.bindVao(null); - - const vao = this.createVao(); - + const CONTEXT_UID = this.CONTEXT_UID; const buffers = geometry.buffers; const attributes = geometry.attributes; @@ -87,26 +194,12 @@ { const buffer = buffers[i]; - if (!buffer._glBuffers[this.CONTEXT_UID]) + if (!buffer._glBuffers[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); - } + buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer()); } } - if (geometry.indexBuffer) - { - // first update the index buffer if we have one.. - vao.addIndex(geometry.indexBuffer._glBuffers[this.CONTEXT_UID]); - } - const tempStride = {}; const tempStart = {}; @@ -118,17 +211,24 @@ for (const j in attributes) { - tempStride[attributes[j].buffer] += glShader.attributes[j].size * byteSizeMap[attributes[j].type]; + if(!attributes[j].size && program.attributeData[j]) + { + attributes[j].size = program.attributeData[j].size; + } + + tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type]; } for (const j in attributes) { const attribute = attributes[j]; - const glAttribute = glShader.attributes[j]; + const attribSize = attribute.size; + + // must be careful that the geometry has attributes not used by this shader.. if (attribute.stride === undefined) { - if (tempStride[attribute.buffer] === glAttribute.size * byteSizeMap[attribute.type]) + if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type]) { attribute.stride = 0; } @@ -142,37 +242,117 @@ { attribute.start = tempStart[attribute.buffer]; - tempStart[attribute.buffer] += glAttribute.size * byteSizeMap[attribute.type]; + tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type]; } } - // next update the attributes buffer.. + // TODO - maybe make this a data object? + // lets wait to see if we need to first! + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + + this.activateVao(geometry, program); + + gl.bindVertexArray(null); + + geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao; + + } + + activateVao(geometry, program) + { + const gl = this.gl; + const CONTEXT_UID = this.CONTEXT_UID; + const buffers = geometry.buffers; + const attributes = geometry.attributes; + + + if (geometry.indexBuffer) + { + // first update the index buffer if we have one.. + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer); + } + + let lastBuffer = null; + + // add a new one! for (const j in attributes) { const attribute = attributes[j]; const buffer = buffers[attribute.buffer]; + const glBuffer = buffer._glBuffers[CONTEXT_UID]; - const glBuffer = buffer._glBuffers[this.CONTEXT_UID]; + if(program.attributeData[j]) + { + if(lastBuffer !== glBuffer) + { + gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer); - // 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); + lastBuffer = glBuffer; + } + + const location = program.attributeData[j].location; + + //TODO introduce state again + //we can optimise this for older devices that have no VAOs + gl.enableVertexAttribArray(location); + + gl.vertexAttribPointer(location, + attribute.size, + attribute.type || gl.FLOAT, + attribute.normalized, + attribute.stride, + attribute.start); + + if(attribute.instance) + { + //TODO calculate instance count based of this... + if(this.hasInstance) + { + gl.vertexAttribDivisor(location, 1); + } + else + { + throw new Error('geometry error, GPU Instancing is not supported on this device'); + } + } + } } - - geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao; - - return vao; } draw(type, size, start, instanceCount) { - this._activeVao.draw(type, size, start, instanceCount); + const gl = this.gl; + const geometry = this._activeGeometry; + + //TODO.. this should not change so maybe cache the function? + + if(geometry.indexBuffer) + { + if(geometry.instanced) + { + gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1); + } + else + { + gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 ); + } + } + else + { + if(geometry.instanced) + { + // TODO need a better way to calculate size.. + gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1); + } + else + { + gl.drawArrays(type, start, size || geometry.getSize()); + } + } + + return this; } /** @@ -193,23 +373,15 @@ */ 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; +class GLBufferData +{ + constructor(buffer) + { + this.buffer = buffer; + this.updateID = -1; + this.byteLength = -1; } } diff --git a/src/core/renderers/webgl/systems/shader/GLShader.js b/src/core/renderers/webgl/systems/shader/GLShader.js index 1344166..6ab6d8d 100644 --- a/src/core/renderers/webgl/systems/shader/GLShader.js +++ b/src/core/renderers/webgl/systems/shader/GLShader.js @@ -5,6 +5,8 @@ setPrecision = require('./shader/setPrecision'), generateUniformAccessObject = require('./shader/generateUniformAccessObject'); +var ID = 0; + /** * Helper class to create a webGL Shader * @@ -65,6 +67,8 @@ this.uniforms = generateUniformAccessObject( gl, this.uniformData ); this.uniformGroups = {}; + + this.id = ID++; }; /** * Uses this shader diff --git a/src/core/renderers/webgl/systems/shader/ShaderSystem.js b/src/core/renderers/webgl/systems/shader/ShaderSystem.js index eb7af23..670dd72 100644 --- a/src/core/renderers/webgl/systems/shader/ShaderSystem.js +++ b/src/core/renderers/webgl/systems/shader/ShaderSystem.js @@ -1,7 +1,7 @@ import WebGLSystem from '../WebGLSystem'; import GLShader from './GLShader'; import { PRECISION } from '../../../../const'; -import generateUniformsSync from '../../../../shader/generateUniformsSync2'; +import generateUniformsSync from '../../../../shader/generateUniformsSync'; let UID = 0; @@ -47,12 +47,13 @@ */ bind(shader, dontSync) { + // maybe a better place for this... + shader.uniforms.globals = this.renderer.globalUniforms; + 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; @@ -80,18 +81,6 @@ 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(); @@ -99,19 +88,20 @@ 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); + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSyncGroups(group); syncFunc(glShader.uniformData, group.uniforms, this.renderer); } } - createSynGroups(group) + createSyncGroups(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. @@ -142,6 +132,8 @@ const program = shader.program; const attribMap = {}; + // insert the global properties too! + for (const i in program.attributeData) { attribMap[i] = program.attributeData[i].location; diff --git a/src/core/renderers/webgl/systems/textures/TextureSystem.js b/src/core/renderers/webgl/systems/textures/TextureSystem.js index 342b5fb..b7fef02 100644 --- a/src/core/renderers/webgl/systems/textures/TextureSystem.js +++ b/src/core/renderers/webgl/systems/textures/TextureSystem.js @@ -206,9 +206,8 @@ } } } - if(texture.target === gl.TEXTURE_2D_ARRAY) + else if(texture.target === gl.TEXTURE_2D_ARRAY) { - console.log("REMEMBER THIS IS TOO MANY!") gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, texture.format, @@ -228,12 +227,8 @@ if(texturePart.resource) { - // void gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, ImageBitmap? pixels); - console.log(texturePart.resource.source) - if(texturePart.resource.loaded) { - console.log("UPOAD..") gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0,//xoffset @@ -246,12 +241,6 @@ texture.type, texturePart.resource.source); } - - } - else - { - - } } } diff --git a/src/core/settings.js b/src/core/settings.js index 38ef45c..bb636aa 100644 --- a/src/core/settings.js +++ b/src/core/settings.js @@ -137,7 +137,7 @@ * @type {PIXI.TRANSFORM_MODE} * @default PIXI.TRANSFORM_MODE.STATIC */ - TRANSFORM_MODE: 0, + TRANSFORM_MODE: 1, /** * Default Garbage Collection mode. @@ -187,7 +187,7 @@ * @type {PIXI.SCALE_MODES} * @default PIXI.SCALE_MODES.LINEAR */ - SCALE_MODE: 1, + SCALE_MODE: 0, /** * Default specify float precision in vertex shader. diff --git a/src/core/shader/Program.js b/src/core/shader/Program.js index ac37001..c5d09cd 100644 --- a/src/core/shader/Program.js +++ b/src/core/shader/Program.js @@ -142,6 +142,8 @@ { const uniformData = gl.getActiveUniform(program, i); const name = uniformData.name.replace(/\[.*?\]/, ''); + + const isArray = uniformData.name.match(/\[.*?\]/, ''); const type = shaderUtils.mapType(gl, uniformData.type); // if (!name.match(maskRegex)) @@ -150,6 +152,7 @@ uniforms[name] = { type: type, size: uniformData.size, + isArray:isArray, value: shaderUtils.defaultValue(type, uniformData.size), }; /*eslint-enable */ diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index ad4fcfc..2f8dc56 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -40,54 +40,41 @@ // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniformGroup.uniforms[i]; - - if (!uniform) // bad as we need check wher ethe uniforms are.. + if (this.uniformGroup.uniforms[i] instanceof Array) { - //this.uniformGroup.uniforms[i] = program.uniformData[i].value; - } - else if (uniform instanceof Array) - { - this.uniformGroup.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(this.uniformGroup.uniforms[i]); } } } + checkUniformExists(name, group) + { + if(group.uniforms[name]) + { + return true; + } + + for (const i in group.uniforms) + { + const uniform = group.uniforms[i]; + + if(uniform.group) + { + if( this.checkUniformExists(name, uniform) ) + { + return true; + } + } + } + + return false; + } + get uniforms() { return this.uniformGroup.uniforms; } -/* -var offsetBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW); - -var colorBuffer = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); - -var instanceCount = 4; -var ext = gl.getExtension("ANGLE_instanced_arrays"); // Vendor prefixes may apply! - -// Bind the rest of the vertex attributes normally -bindMeshArrays(gl); - -// Bind the instance position data -gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); -gl.enableVertexAttribArray(offsetLocation); -gl.vertexAttribPointer(offsetLocation, 3, gl.FLOAT, false, 12, 0); -ext.vertexAttribDivisorANGLE(offsetLocation, 1); // This makes it instanced! - -// Bind the instance color data -gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); -gl.enableVertexAttribArray(colorLocation); -gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, false, 16, 0); -ext.vertexAttribDivisorANGLE(colorLocation, 1); // This makes it instanced! - -// Draw the instanced meshes -ext.drawElementsInstancedANGLE(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount); -*/ /** * A short hand function to create a shader based of a vertex and fragment shader * diff --git a/src/core/shader/UniformGroup.js b/src/core/shader/UniformGroup.js index 8768226..e2ed22a 100644 --- a/src/core/shader/UniformGroup.js +++ b/src/core/shader/UniformGroup.js @@ -1,5 +1,3 @@ -import Program from './Program'; -import generateUniformsSync from './generateUniformsSync'; let UID = 0; diff --git a/src/core/shader/generateUniformsSync.js b/src/core/shader/generateUniformsSync.js index b7241ef..8a7cf55 100644 --- a/src/core/shader/generateUniformsSync.js +++ b/src/core/shader/generateUniformsSync.js @@ -1,153 +1,190 @@ + + +//cv = CachedValue +//v = value +//ud = uniformData +//uv = uniformValue +//l = loaction const GLSL_TO_SINGLE_SETTERS_CACHED = { - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, + float:` + if(cv !== v) + { + cv.v = v; + gl.uniform1f(location, v) + }`, - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; + vec2:` + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]) + }`, - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', + vec3:` + if(cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) + { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + gl.uniform3f(location, v[0], v[1], v[2]) + }`, - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', + vec4: 'gl.uniform4f(location, v[0], v[1], v[2], v[3])', - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', + int: 'gl.uniform1i(location, v)', + ivec2: 'gl.uniform2i(location, v[0], v[1])', + ivec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + ivec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)' + bool: 'gl.uniform1i(location, v)', + bvec2: 'gl.uniform2i(location, v[0], v[1])', + bvec3: 'gl.uniform3i(location, v[0], v[1], v[2])', + bvec4: 'gl.uniform4i(location, v[0], v[1], v[2], v[3])', + + mat2: 'gl.uniformMatrix2fv(location, false, v)', + mat3: 'gl.uniformMatrix3fv(location, false, v)', + mat4: 'gl.uniformMatrix4fv(location, false, v)', + + sampler2D: 'gl.uniform1i(location, v)', + samplerCube: 'gl.uniform1i(location, v)', + sampler2DArray: 'gl.uniform1i(location, v)', }; const GLSL_TO_ARRAY_SETTERS = { - float: `gl.uniform1fv(location, value)`, + float: `gl.uniform1fv(location, v)`, - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', + vec2: `gl.uniform2fv(location, v)`, + vec3: `gl.uniform3fv(location, v)`, + vec4: 'gl.uniform4fv(location, v)', - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', + int: 'gl.uniform1iv(location, v)', + ivec2: 'gl.uniform2iv(location, v)', + ivec3: 'gl.uniform3iv(location, v)', + ivec4: 'gl.uniform4iv(location, v)', - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', + bool: 'gl.uniform1iv(location, v)', + bvec2: 'gl.uniform2iv(location, v)', + bvec3: 'gl.uniform3iv(location, v)', + bvec4: 'gl.uniform4iv(location, v)', - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)' + sampler2D: 'gl.uniform1iv(location, v)', + samplerCube: 'gl.uniform1iv(location, v)', + sampler2DArray: 'gl.uniform1iv(location, v)', }; -export default function generateUniformsSync(uniformData) + +export default function generateUniformsSync(group, uniformData) { - let textureCount = 1; - let func = `var value = null; - var cacheValue = null + let textureCount = 0; + let func = `var v = null; + var cv = null var gl = renderer.gl`; - for (const i in uniformData) + for (const i in group.uniforms) { const data = uniformData[i]; - // console.log(i, data.size); + + if(!data) + { + if(group.uniforms[i].group) + { + func += ` + renderer.shader.syncUniformGroup(uv.${i}); + ` + } + + continue; + } // TODO && uniformData[i].value !== 0 <-- do we still need this? if (data.type === 'float' && data.size === 1) { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; + func += ` + if(uv.${i} !== ud.${i}.value) + { + ud.${i}.value = uv.${i} + gl.uniform1f(ud.${i}.location, uv.${i}) + }\n`; } - else if (data.type === 'sampler2D' && data.size === 1) + else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1 && !data.isArray) { - func += `\nif (uniformValues.${i}.baseTexture) -{ - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount++}, false); + func += ` + renderer.texture.bind(uv.${i}, ${textureCount}); - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - } -} -else -{ - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + if(ud.${i}.value !== ${textureCount}) + { + ud.${i}.value = ${textureCount}; + gl.uniform1i(ud.${i}.location, ${textureCount});\n; // eslint-disable-line max-len + }\n` - uniformValues.${i}.bind(); -}`; + textureCount++; } else if (data.type === 'mat3' && data.size === 1) { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; + if(group.uniforms[i].a !== undefined) + { + // TODO and some smart caching dirty ids here! + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}.toArray(true)); + \n`; + } + else + { + func += ` + gl.uniformMatrix3fv(ud.${i}.location, false, uv.${i}); + \n`; + } } else if (data.type === 'vec2' && data.size === 1) { // TODO - do we need both here? // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; + if(group.uniforms[i].x !== undefined) + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; + if(cv[0] !== v.x || cv[1] !== v.y) + { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud.${i}.location, v.x, v.y); + }\n`; + } + else + { + func += ` + cv = ud.${i}.value; + v = uv.${i}; + + if(cv[0] !== v[0] || cv[1] !== v[1]) + { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(ud.${i}.location, v[0], v[1]); + } + }\n`; + } } else { const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); + const template = templateType[data.type].replace('location', `ud.${i}.location`); - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; + func += ` + cv = ud.${i}.value; + v = uv.${i}; + ${template};\n`; } } - //console.log(' --------------- ') - // console.log(func); + // console.log(' --------------- ') + // console.log(func); - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func + return new Function('ud', 'uv', 'renderer', func); // eslint-disable-line no-new-func } diff --git a/src/core/shader/generateUniformsSync2.js b/src/core/shader/generateUniformsSync2.js deleted file mode 100644 index 8a0fd6b..0000000 --- a/src/core/shader/generateUniformsSync2.js +++ /dev/null @@ -1,190 +0,0 @@ -const GLSL_TO_SINGLE_SETTERS_CACHED = { - - float: `if(cacheValue !== value) -{ - cacheValue.value = value; - gl.uniform1f(location, value) -}`, - - vec2: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(location, value[0], value[1]) -}`, - vec3: `if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1] || cacheValue[2] !== value[2]) -{ - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - cacheValue[2] = value[2]; - - gl.uniform3f(location, value[0], value[1], value[2]) -}`, - vec4: 'gl.uniform4f(location, value[0], value[1], value[2], value[3])', - - int: 'gl.uniform1i(location, value)', - ivec2: 'gl.uniform2i(location, value[0], value[1])', - ivec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - ivec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - bool: 'gl.uniform1i(location, value)', - bvec2: 'gl.uniform2i(location, value[0], value[1])', - bvec3: 'gl.uniform3i(location, value[0], value[1], value[2])', - bvec4: 'gl.uniform4i(location, value[0], value[1], value[2], value[3])', - - mat2: 'gl.uniformMatrix2fv(location, false, value)', - mat3: 'gl.uniformMatrix3fv(location, false, value)', - mat4: 'gl.uniformMatrix4fv(location, false, value)', - - sampler2D: 'gl.uniform1i(location, value)', - samplerCube: 'gl.uniform1i(location, value)', - sampler2DArray: 'gl.uniform1i(location, value)', -}; - -const GLSL_TO_ARRAY_SETTERS = { - - float: `gl.uniform1fv(location, value)`, - - vec2: `gl.uniform2fv(location, value)`, - vec3: `gl.uniform3fv(location, value)`, - vec4: 'gl.uniform4fv(location, value)', - - int: 'gl.uniform1iv(location, value)', - ivec2: 'gl.uniform2iv(location, value)', - ivec3: 'gl.uniform3iv(location, value)', - ivec4: 'gl.uniform4iv(location, value)', - - bool: 'gl.uniform1iv(location, value)', - bvec2: 'gl.uniform2iv(location, value)', - bvec3: 'gl.uniform3iv(location, value)', - bvec4: 'gl.uniform4iv(location, value)', - - sampler2D: 'gl.uniform1iv(location, value)', - samplerCube: 'gl.uniform1iv(location, value)', - sampler2DArray: 'gl.uniform1iv(location, value)', -}; - -export default function generateUniformsSync2(group, uniformData) -{ - let textureCount = 0; - let func = `var value = null; - var cacheValue = null - var gl = renderer.gl`; - - - for (const i in group.uniforms) - { - const data = uniformData[i]; - //group.uniforms[i]; - // console.log(i, data); - if(!data) - { - if(group.uniforms[i].group) - { - func += ` - renderer.shader.syncUniformGroup(uniformValues.${i}); - ` - } - - continue; - } - - // TODO && uniformData[i].value !== 0 <-- do we still need this? - if (data.type === 'float' && data.size === 1) - { - func += `\nif(uniformValues.${i} !== uniformData.${i}.value) -{ - uniformData.${i}.value = uniformValues.${i} - gl.uniform1f(uniformData.${i}.location, uniformValues.${i}) -}\n`; - } - else if ( (data.type === 'sampler2D' || data.type === 'samplerCube' || data.type === 'sampler2DArray') && data.size === 1) - { - func += `\nif (uniformValues.${i}.baseTexture) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } - - /* - var location = renderer.bindTexture(uniformValues.${i}.baseTexture, ${textureCount}, false); - - if(uniformData.${i}.value !== location) - { - uniformData.${i}.value = location; - gl.uniform1i(uniformData.${i}.location, location);\n; // eslint-disable-line max-len - }*/ -} -else if(uniformValues.${i}._new) -{ - - renderer.texture.bind(uniformValues.${i}, ${textureCount}) - if(uniformData.${i}.value !== ${textureCount}) - { - uniformData.${i}.value = ${textureCount}; - gl.uniform1i(uniformData.${i}.location, ${textureCount});\n; // eslint-disable-line max-len - } -} -else -{ - // console.log("binding..") - uniformData.${i}.value = ${textureCount}; - renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; - gl.activeTexture(gl.TEXTURE0 + ${textureCount}); - - uniformValues.${i}.bind(); -}`; - textureCount++; - } - else if (data.type === 'mat3' && data.size === 1) - { - func += `\nvalue = uniformValues.${i}; -gl.uniformMatrix3fv(uniformData.${i}.location, false, (value.a === undefined) ? value : value.toArray(true));\n`; - } - else if (data.type === 'vec2' && data.size === 1) - { - // TODO - do we need both here? - // maybe we can get away with only using points? - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; - -if(value.x !== undefined) -{ - if(cacheValue[0] !== value.x || cacheValue[1] !== value.y) - { - cacheValue[0] = value.x; - cacheValue[1] = value.y; - gl.uniform2f(uniformData.${i}.location, value.x, value.y); - } -} -else -{ - if(cacheValue[0] !== value[0] || cacheValue[1] !== value[1]) - { - cacheValue[0] = value[0]; - cacheValue[1] = value[1]; - gl.uniform2f(uniformData.${i}.location, value[0], value[1]); - } -}\n`; - } - else - { - const templateType = (data.size === 1) ? GLSL_TO_SINGLE_SETTERS_CACHED : GLSL_TO_ARRAY_SETTERS; - - const template = templateType[data.type].replace('location', `uniformData.${i}.location`); - - func += `\ncacheValue = uniformData.${i}.value; -value = uniformValues.${i}; -${template};\n`; - } - } - - console.log(' --------------- ') - console.log(func); - - return new Function('uniformData', 'uniformValues', 'renderer', func); // eslint-disable-line no-new-func -} diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 5b486ba..57b4b3c 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -7,6 +7,10 @@ import Buffer from './BatchBuffer'; import settings from '../../settings'; import bitTwiddle from 'bit-twiddle'; +import Geometry from '../../geometry/Geometry'; +//TODO rename this +import Buffer_GEOM from '../../geometry/Buffer'; + let TICK = 0; let TEXTURE_TICK = 0; @@ -65,6 +69,7 @@ * @member {Uint16Array} */ this.indices = createIndicesForQuads(this.size); + this.indexBuffer = new Buffer_GEOM(this.indices, true, true); /** * The default shaders that is used if a sprite doesn't have a more specific one. @@ -115,50 +120,27 @@ this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl); } - const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); - const sampleValues = new Int32Array(this.MAX_TEXTURES); - - for (let i = 0; i < this.MAX_TEXTURES; i++) - { - sampleValues[i] = i; - } - - shader.uniformGroup.add('default', {uSamplers:sampleValues}, true);//this.renderer.globalUniforms; - shader.uniforms.globals = this.renderer.globalUniforms; - - this.indexBuffer = GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + // generate generateMultiTextureProgram, may be a better move? + this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES); // we use the second shader as the first one depending on your browser may omit aTextureId // as it is not used by the shader so is optimized out. - - this.renderer.geometry.bindVao(null); - for (let i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + var buffer = new Buffer_GEOM(null, false); + /* eslint-disable max-len */ - - var attributeData = shader.program.attributeData; - // build the vao object that will render.. - this.vaos[i] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[i].addAttribute(this.vertexBuffers[i], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[i] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[i] = buffer; } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; - - this.boundTextures = new Array(this.MAX_TEXTURES); } /** @@ -221,9 +203,7 @@ const float32View = buffer.float32View; const uint32View = buffer.uint32View; - const boundTextures = this.boundTextures; - const rendererBoundTextures = this.renderer.boundTextures; - const touch = 0//this.renderer.textureGC.count; + const touch = 0;//this.renderer.textureGC.count; let index = 0; let nextTexture; @@ -244,13 +224,6 @@ let i; - // copy textures.. - for (i = 0; i < MAX_TEXTURES; ++i) - { - boundTextures[i] = rendererBoundTextures[i]; - boundTextures[i]._virtalBoundId = i; - } - for (i = 0; i < this.currentIndex; ++i) { // upload the sprite elemetns... @@ -371,37 +344,32 @@ if (this.vaoMax <= this.vertexCount) { this.vaoMax++; - this.vertexBuffers[this.vertexCount] = GLBuffer.createVertexBuffer(gl, null, gl.DYNAMIC_DRAW); + + let buffer = new Buffer_GEOM(null, false); /* eslint-disable max-len */ - - var attributeData = this.shader.program.attributeData; - - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.geometry.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4); - - if (attributeData.aTextureId) - { - this.vaos[this.vertexCount].addAttribute(this.vertexBuffers[this.vertexCount], attributeData.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - + this.vaos[this.vertexCount] = new Geometry() + .addAttribute('aVertexPosition', buffer, 2, false, gl.FLOAT) + .addAttribute('aTextureCoord', buffer, 2, true, gl.UNSIGNED_SHORT) + .addAttribute('aColor', buffer, 4, true, gl.UNSIGNED_BYTE) + .addAttribute('aTextureId', buffer, 1, true, gl.FLOAT) + .addIndex(this.indexBuffer) /* eslint-enable max-len */ + + this.vertexBuffers[this.vertexCount] = buffer; } - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); this.vertexCount++; } else { // lets use the faster option, always use buffer number 0 - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, true); + this.vertexBuffers[this.vertexCount].update(buffer.vertices, 0); + + this.renderer.geometry.updateBuffers(); } /// render the groups.. @@ -409,15 +377,6 @@ var group = groups[i]; var groupTextureCount = group.textureCount; - //shader = this.shaders[groupTextureCount-1]; - - //if(!shader) - // { - // shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - //} - - /// this.renderer.shader.bind(shader); for (var j = 0; j < groupTextureCount; j++) { @@ -425,38 +384,11 @@ } // set the blend mode.. - //this.renderer.state.setBlendMode( group.blend ); + this.renderer.state.setBlendMode( group.blend ); gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); } -/* - // render the groups.. - for (i = 0; i < groupCount; ++i) - { - const group = groups[i]; - const groupTextureCount = group.textureCount; - for (let j = 0; j < groupTextureCount; j++) - { - currentTexture = group.textures[j]; - - // reset virtual ids.. - // lets do a quick check.. - if (rendererBoundTextures[group.ids[j]] !== currentTexture) - { - this.renderer.texture.bind(currentTexture, group.ids[j], true); - } - - // reset the virtualId.. - currentTexture._virtalBoundId = -1; - } - - // set the blend mode.. - this.renderer.state.setBlendMode(group.blend); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } -*/ // reset elements for the next flush this.currentIndex = 0; } @@ -466,16 +398,12 @@ */ start() { - // this.renderer._bindGLShader(this.shader); - this.renderer.shader.bind(this.shader, true); - this.renderer.shader.syncUniformGroup(this.shader.uniformGroup); + this.renderer.shader.bind(this.shader); if (settings.CAN_UPLOAD_SAME_BUFFER) { // bind buffer #0, we don't need others - this.renderer.geometry.bindVao(this.vaos[this.vertexCount]); - - this.vertexBuffers[this.vertexCount].bind(); + this.renderer.geometry.bind(this.vaos[this.vertexCount]); } } diff --git a/src/core/sprites/webgl/generateMultiTextureShader.js b/src/core/sprites/webgl/generateMultiTextureShader.js index 1dabb7a..cab5255 100644 --- a/src/core/sprites/webgl/generateMultiTextureShader.js +++ b/src/core/sprites/webgl/generateMultiTextureShader.js @@ -1,5 +1,5 @@ -import { GLShader } from 'pixi-gl-core'; import Shader from '../../shader/Shader'; +import UniformGroup from '../../shader/UniformGroup'; import { PRECISION } from '../../const'; import { readFileSync } from 'fs'; import { join } from 'path'; @@ -20,25 +20,25 @@ export default function generateMultiTextureShader(gl, maxTextures) { - const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); - let fragmentSrc = fragTemplate; - - fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); - fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); - - const shaderold = new GLShader(gl, vertexSrc, fragmentSrc, PRECISION.DEFAULT); - const shader = Shader.from(vertexSrc, fragmentSrc);//, PRECISION.DEFAULT); - - const sampleValues = []; + const sampleValues = new Int32Array(maxTextures); for (let i = 0; i < maxTextures; i++) { sampleValues[i] = i; } - // shader.bind(); - // shader.uniforms.uSamplers = new Int32Array(sampleValues); - // shader.uniformGroup.static = true; + const uniforms = { + default:UniformGroup.from({uSamplers:sampleValues}, true), + } + + + const vertexSrc = readFileSync(join(__dirname, './texture.vert'), 'utf8'); + let fragmentSrc = fragTemplate; + + fragmentSrc = fragmentSrc.replace(/%count%/gi, maxTextures); + fragmentSrc = fragmentSrc.replace(/%forloop%/gi, generateSampleSrc(maxTextures)); + + const shader = Shader.from(vertexSrc, fragmentSrc, uniforms);//, PRECISION.DEFAULT); return shader; } diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 57c4cc5..13417a1 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -450,6 +450,7 @@ } } +BaseTexture.fromFrame = BaseTexture.fromFrame; BaseTexture.fromImage = BaseTexture.from; BaseTexture.fromSVG = BaseTexture.from; BaseTexture.fromCanvas = BaseTexture.from; \ No newline at end of file diff --git a/src/core/textures/resources/VideoResource.js b/src/core/textures/resources/VideoResource.js index 7d525c0..3402f96 100644 --- a/src/core/textures/resources/VideoResource.js +++ b/src/core/textures/resources/VideoResource.js @@ -58,6 +58,7 @@ update() { + //TODO - slow down and base on the videos framerate this.resourceUpdated.emit(); } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 3fdd996..609da80 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -252,7 +252,7 @@ if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) { const args = [ - `\n %c %c %c Pixi.js ${VERSION} - ✰ ${type} ✰ %c %c http://www.pixijs.com/ %c %c ♥%c♥%c♥ \n\n`, + `\n %c %c %c Pixi.js ${VERSION} - ✰✰ ${type} ✰✰ %c %c http://www.pixijs.com/ %c %c ♥%c♥%c♥ \n\n`, 'background: #ff66a5; padding:5px 0;', 'background: #ff66a5; padding:5px 0;', 'color: #ff66a5; background: #030307; padding:5px 0;', diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index dd11031..ec46e24 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -1,5 +1,5 @@ import RawMesh from './RawMesh'; -import Geometry from './geometry/Geometry'; +import Geometry from '../core/geometry/Geometry'; import * as core from '../core'; import { readFileSync } from 'fs'; import { join } from 'path'; diff --git a/src/mesh/geometry/Attribute.js b/src/mesh/geometry/Attribute.js deleted file mode 100644 index 83f92c2..0000000 --- a/src/mesh/geometry/Attribute.js +++ /dev/null @@ -1,58 +0,0 @@ -/* eslint-disable max-len */ - -/** - * holds the information for a single attribute structure required to render geometry. - * this does not conatina the actul data, but instead has a buffer id that maps to a {PIXI.mesh.Buffer} - * This can include anything from positions, uvs, normals, colors etc.. - * - * @class - * @memberof PIXI.mesh.Attribute - */ -class Attribute -{ - /** - * @param {string} buffer the id of the buffer that this attribute will look for - * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2. - * @param {Boolean} [normalised=false] should the data be normalised. - * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available - * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) - * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) - */ - constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) - { - this.buffer = buffer; - this.size = size; - this.normalized = normalised; - this.type = type; - this.stride = stride; - this.start = start; - this.instance = instance; - } - - /** - * Destroys the Attribute. - */ - destroy() - { - this.buffer = null; - } - - /** - * Helper function that creates an Attribute based on the information provided - * - * @static - * @param {string} buffer the id of the buffer that this attribute will look for - * @param {Number} [size=2] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 - * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) - * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) - * @param {Boolean} [normalised=false] should the data be normalised. - * - * @returns {PIXI.mesh.Attribute} A new {PIXI.mesh.Attribute} based on the information provided - */ - static from(buffer, size, stride, start, normalised) - { - return new Attribute(buffer, size, stride, start, normalised); - } -} - -module.exports = Attribute; diff --git a/src/mesh/geometry/Buffer.js b/src/mesh/geometry/Buffer.js deleted file mode 100644 index 43d49bf..0000000 --- a/src/mesh/geometry/Buffer.js +++ /dev/null @@ -1,80 +0,0 @@ -let UID = 0; -/* eslint-disable max-len */ - -/** - * A wrapper for data so that it can be used and uploaded by webGL - * - * @class - * @memberof PIXI - */ -export default class Buffer -{ - /** - * @param {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the data to store in the buffer. - */ - constructor(data) - { - /** - * The data in the buffer, as a typed array - * - * @type {ArrayBuffer| SharedArrayBuffer|ArrayBufferView} data the array / typedArray - */ - this.data = data; - - /** - * A map of renderer IDs to webgl buffer - * - * @private - * @member {object} - */ - this._glBuffers = []; - - this._updateID = 0; - - this.index = false; - - this.static = true; - - this.id = UID++; - } - - // TODO could explore flagging only a partial upload? - /** - * flags this buffer as requiring an upload to the GPU - */ - update() - { - this._updateID++; - } - - /** - * Destroys the buffer - */ - destroy() - { - for (let i = 0; i < this._glBuffers.length; i++) - { - this._glBuffers[i].destroy(); - } - - this.data = null; - } - - /** - * Helper function that creates a buffer based on an array or TypedArray - * - * @static - * @param {TypedArray| Array} data the TypedArray that the buffer will store. If this is a regular Array it will be converted to a Float32Array. - * @return {PIXI.mesh.Buffer} A new Buffer based on the data provided. - */ - static from(data) - { - if (data instanceof Array) - { - data = new Float32Array(data); - } - - return new Buffer(data); - } -} - diff --git a/src/mesh/geometry/Geometry.js b/src/mesh/geometry/Geometry.js deleted file mode 100644 index c773eac..0000000 --- a/src/mesh/geometry/Geometry.js +++ /dev/null @@ -1,389 +0,0 @@ -import Attribute from './Attribute'; -import Buffer from './Buffer'; -import interleaveTypedArrays from '../../core/utils/interleaveTypedArrays'; -import getBufferType from '../../core/utils/getBufferType'; - -const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 }; -let UID = 0; - -/* eslint-disable object-shorthand */ -const map = { - Float32Array: Float32Array, - Uint32Array: Uint32Array, - Int32Array: Int32Array, - Uint16Array: Uint16Array, -}; - -/* eslint-disable max-len */ - -/** - * The Geometry represents a model. It consists of two components: - * GeometryStyle - The structure of the model such as the attributes layout - * GeometryData - the data of the model - this consits of buffers. - * - * This can include anything from positions, uvs, normals, colors etc.. - * - * Geometry can be defined without passing in a style or data if required (thats how I prefer!) - * - * ```js - * let geometry = new PIXI.mesh.Geometry(); - * - * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2); - * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2) - * geometry.addIndex([0,1,2,1,3,2]) - * - * ``` - * @class - * @memberof PIXI.mesh.Geometry - */ -export default class Geometry -{ - /** - * @param {array} buffers an array of buffers. optional. - * @param {object} attributes of the geometry, optional structure of the attributes layout - */ - constructor(buffers, attributes) - { - this.buffers = buffers || []; - - this.indexBuffer = null; - - this.attributes = attributes || {}; - - /** - * A map of renderer IDs to webgl VAOs - * - * @private - * @type {Array} - */ - this.glVertexArrayObjects = []; - - this.id = UID++; - } - - /** - * - * Adds an attribute to the geometry - * - * @param {String} id - the name of the attribute (matching up to a shader) - * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it. - * @param {Number} [size=0] the size of the attribute. If you hava 2 floats per vertex (eg position x and y) this would be 2 - * @param {Boolean} [normalised=false] should the data be normalised. - * @param {Number} [type=PIXI.TYPES.FLOAT] what type of numbe is the attribute. Check {PIXI.TYPES} to see the ones available - * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data) - * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data) - * - * @return {PIXI.mesh.Geometry} returns self, useful for chaining. - */ - addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) - { - if (!buffer) - { - throw new Error('You must pass a buffer when creating an attribute'); - } - - // check if this is a buffer! - if (!buffer.data) - { - // its an array! - if (buffer instanceof Array) - { - buffer = new Float32Array(buffer); - } - - buffer = new Buffer(buffer); - } - - const ids = id.split('|'); - - if (ids.length > 1) - { - for (let i = 0; i < ids.length; i++) - { - this.addAttribute(ids[i], buffer, size, normalised, type); - } - - return this; - } - - let bufferIndex = this.buffers.indexOf(buffer); - - if (bufferIndex === -1) - { - this.buffers.push(buffer); - bufferIndex = this.buffers.length - 1; - } - - this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); - - return this; - } - - /** - * returns the requested attribute - * - * @param {String} id the name of the attribute required - * @return {PIXI.mesh.Attribute} the attribute requested. - */ - getAttribute(id) - { - return this.buffers[this.attributes[id].buffer]; - } - - /** - * - * Adds an index buffer to the geometry - * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer. - * - * @param {PIXI.mesh.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it. - * @return {PIXI.mesh.Geometry} returns self, useful for chaining. - */ - addIndex(buffer) - { - if (!buffer.data) - { - // its an array! - if (buffer instanceof Array) - { - buffer = new Uint16Array(buffer); - } - - buffer = new Buffer(buffer); - } - - buffer.index = true; - this.indexBuffer = buffer; - - if (this.buffers.indexOf(buffer) === -1) - { - this.buffers.push(buffer); - } - - return this; - } - - /** - * returns the index buffer - * - * @return {PIXI.mesh.Buffer} the index buffer. - */ - getIndex() - { - return this.indexBuffer; - } - - /** - * this function modifies the structure so that all current attributes become interleaved into a single buffer - * This can be useful if your model remains static as it offers a little performance boost - * - * @return {PIXI.mesh.Geometry} returns self, useful for chaining. - */ - interleave() - { - // a simple check to see if buffers are already interleaved.. - if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this; - - // assume already that no buffers are interleaved - const arrays = []; - const sizes = []; - const interleavedBuffer = new Buffer(); - let i; - - for (i in this.attributes) - { - const attribute = this.attributes[i]; - - const buffer = this.buffers[attribute.buffer]; - - arrays.push(buffer.data); - - sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4); - - attribute.buffer = 0; - } - - interleavedBuffer.data = interleaveTypedArrays(arrays, sizes); - - for (i = 0; i < this.buffers.length; i++) - { - if (this.buffers[i] !== this.indexBuffer) - { - this.buffers[i].destroy(); - } - } - - this.buffers = [interleavedBuffer]; - - if (this.indexBuffer) - { - this.buffers.push(this.indexBuffer); - } - - return this; - } - - /** - * Destroys the geometry. - */ - destroy() - { - for (let i = 0; i < this.glVertexArrayObjects.length; i++) - { - this.glVertexArrayObjects[i].destroy(); - } - - this.glVertexArrayObjects = null; - - for (let i = 0; i < this.buffers.length; i++) - { - this.buffers[i].destroy(); - } - - this.buffers = null; - this.indexBuffer.destroy(); - - this.attributes = null; - } - - /** - * returns a clone of the geometry - * - * @returns {PIXI.mesh.Geometry} a new clone of this geometry - */ - clone() - { - const geometry = new Geometry(); - - for (let i = 0; i < this.buffers.length; i++) - { - geometry.buffers[i] = new Buffer(this.buffers[i].data.slice()); - } - - for (const i in this.attributes) - { - const attrib = this.attributes[i]; - - geometry.attributes[i] = new Attribute( - attrib.buffer, - attrib.size, - attrib.normalized, - attrib.type, - attrib.stride, - attrib.start, - attrib.instance - ); - } - - if (this.indexBuffer) - { - geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)]; - geometry.indexBuffer.index = true; - } - - return geometry; - } - - /** - * merges an array of geometries into a new single one - * geometry attribute styles must match for this operation to work - * - * @param {array|PIXI.mesh.Geometry} geometries array of geometries to merge - * @returns {PIXI.mesh.Geometry} shiney new geometry - */ - static merge(geometries) - { - // todo add a geometry check! - // also a size check.. cant be too big!] - - const geometryOut = new Geometry(); - - const arrays = []; - const sizes = []; - const offsets = []; - - let geometry; - - // pass one.. get sizes.. - for (let i = 0; i < geometries.length; i++) - { - geometry = geometries[i]; - - for (let j = 0; j < geometry.buffers.length; j++) - { - sizes[j] = sizes[j] || 0; - sizes[j] += geometry.buffers[j].data.length; - offsets[j] = 0; - } - } - - // build the correct size arrays.. - for (let i = 0; i < geometry.buffers.length; i++) - { - // TODO types! - arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]); - geometryOut.buffers[i] = new Buffer(arrays[i]); - } - - // pass to set data.. - for (let i = 0; i < geometries.length; i++) - { - geometry = geometries[i]; - - for (let j = 0; j < geometry.buffers.length; j++) - { - arrays[j].set(geometry.buffers[j].data, offsets[j]); - offsets[j] += geometry.buffers[j].data.length; - } - } - - geometryOut.attributes = geometry.attributes; - - if (geometry.indexBuffer) - { - geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)]; - geometryOut.indexBuffer.index = true; - - let offset = 0; - let stride = 0; - let offset2 = 0; - let bufferIndexToCount = 0; - - // get a buffer - for (let i = 0; i < geometry.buffers.length; i++) - { - if (geometry.buffers[i] !== geometry.indexBuffer) - { - bufferIndexToCount = i; - break; - } - } - - // figure out the stride of one buffer.. - for (const i in geometry.attributes) - { - const attribute = geometry.attributes[i]; - - if ((attribute.buffer | 0) === bufferIndexToCount) - { - stride += ((attribute.size * byteSizeMap[attribute.type]) / 4); - } - } - - // time to off set all indexes.. - for (let i = 0; i < geometries.length; i++) - { - const indexBufferData = geometries[i].indexBuffer.data; - - for (let j = 0; j < indexBufferData.length; j++) - { - geometryOut.indexBuffer.data[j + offset2] += offset; - } - - offset += geometry.buffers[bufferIndexToCount].data.length / (stride); - offset2 += indexBufferData.length; - } - } - - return geometryOut; - } -} diff --git a/src/mesh/index.js b/src/mesh/index.js index 9094f68..e4a24cd 100644 --- a/src/mesh/index.js +++ b/src/mesh/index.js @@ -5,9 +5,9 @@ export { default as RawMesh } from './RawMesh'; export { default as MeshRenderer } from './webgl/MeshRenderer'; export { default as CanvasMeshRenderer } from './canvas/CanvasMeshRenderer'; -export { default as Attribute } from './geometry/Attribute'; -export { default as Buffer } from './geometry/Buffer'; -export { default as Geometry } from './geometry/Geometry'; +export { default as Attribute } from '../core/geometry/Attribute'; +export { default as Buffer } from '../core/geometry/Buffer'; +export { default as Geometry } from '../core/geometry/Geometry'; export { default as Plane } from './Plane'; export { default as NineSlicePlane } from './NineSlicePlane'; export { default as Rope } from './Rope'; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index ab99fcd..1089c3c 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -45,25 +45,27 @@ render(mesh) { // bind the shader.. - const glShader = this.renderer.shader.bind(mesh.shader, true); + // TODO // set the shader props.. - //if (glShader.uniformData.translationMatrix) - //{ + // probably only need to set once! + // as its then a refference.. + if (mesh.shader.program.uniformData.translationMatrix) + { // the transform! - // glShader.uniforms.translationMatrix = mesh.transform.worldTransform.toArray(true); - //} + mesh.shader.uniforms.translationMatrix = mesh.transform.worldTransform.toArray(true); + } - // set unifomrs.. - this.renderer.shader.syncUniformGroup(mesh.shader.uniformGroup); + // bind and sync uniforms.. + this.renderer.shader.bind(mesh.shader); - // sync uniforms.. + // set state.. this.renderer.state.setState(mesh.state); // bind the geometry... - this.renderer.geometry.bind(mesh.geometry, glShader); + this.renderer.geometry.bind(mesh.geometry, mesh.shader); // then render it - mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); + this.renderer.geometry.draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); }