diff --git a/src/core/index.js b/src/core/index.js index 4f414e8..9e4f1f1 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -45,6 +45,7 @@ export { default as Quad } from './renderers/webgl/utils/Quad'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; +export { default as UniformGroup } from './shader/UniformGroup'; export { default as SpriteMaskFilter } from './renderers/webgl/filters/spriteMask/SpriteMaskFilter'; export { default as Filter } from './renderers/webgl/filters/Filter'; export { default as Application } from './Application'; diff --git a/src/core/index.js b/src/core/index.js index 4f414e8..9e4f1f1 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -45,6 +45,7 @@ export { default as Quad } from './renderers/webgl/utils/Quad'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; +export { default as UniformGroup } from './shader/UniformGroup'; export { default as SpriteMaskFilter } from './renderers/webgl/filters/spriteMask/SpriteMaskFilter'; export { default as Filter } from './renderers/webgl/filters/Filter'; export { default as Application } from './Application'; diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js index b4fdfc9..290d4ba 100644 --- a/src/core/renderers/webgl/ShaderManager.js +++ b/src/core/renderers/webgl/ShaderManager.js @@ -1,5 +1,8 @@ import { GLShader } from 'pixi-gl-core'; import { PRECISION } from '../../const'; +import generateUniformsSync from '../../shader/generateUniformsSync2'; + +let UID = 0; /** * Helper class to create a webGL Texture @@ -29,6 +32,8 @@ this.gl = renderer.gl; this.shader = null; + + this.id = UID++; } /** @@ -71,6 +76,38 @@ 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.uniformD1ata, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } /** * Returns the underlying GLShade rof the currently bound shader. * This can be handy for when you to have a little more control over the setting of your uniforms. diff --git a/src/core/index.js b/src/core/index.js index 4f414e8..9e4f1f1 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -45,6 +45,7 @@ export { default as Quad } from './renderers/webgl/utils/Quad'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; +export { default as UniformGroup } from './shader/UniformGroup'; export { default as SpriteMaskFilter } from './renderers/webgl/filters/spriteMask/SpriteMaskFilter'; export { default as Filter } from './renderers/webgl/filters/Filter'; export { default as Application } from './Application'; diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js index b4fdfc9..290d4ba 100644 --- a/src/core/renderers/webgl/ShaderManager.js +++ b/src/core/renderers/webgl/ShaderManager.js @@ -1,5 +1,8 @@ import { GLShader } from 'pixi-gl-core'; import { PRECISION } from '../../const'; +import generateUniformsSync from '../../shader/generateUniformsSync2'; + +let UID = 0; /** * Helper class to create a webGL Texture @@ -29,6 +32,8 @@ this.gl = renderer.gl; this.shader = null; + + this.id = UID++; } /** @@ -71,6 +76,38 @@ 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.uniformD1ata, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } /** * Returns the underlying GLShade rof the currently bound shader. * This can be handy for when you to have a little more control over the setting of your uniforms. diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index 3768f03..b05379a 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -1,4 +1,5 @@ import Program from './Program'; +import UniformGroup from './UniformGroup'; // let math = require('../../../math'); /** @@ -15,26 +16,78 @@ constructor(program, uniforms) { this.program = program; - this.uniforms = uniforms || {}; + + // lets see whats been passed in + // uniforms should be converted to a uniform group + if(uniforms) + { + if(uniforms instanceof UniformGroup) + { + this.uniformGroup = uniforms; + } + else + { + this.uniformGroup = new UniformGroup(uniforms) + } + } + else + { + this.uniformGroup = new UniformGroup({}); + } // time to build some getters and setters! // I guess down the line this could sort of generate an instruction list rather than use dirty ids? // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniforms[i]; + const uniform = this.uniformGroup.uniforms[i]; - if (!uniform) + if (!uniform) // bad as we need check wher ethe uniforms are.. { - this.uniforms[i] = program.uniformData[i].value; + //this.uniforms.uniforms[i] = program.uniformData[i].value; } else if (uniform instanceof Array) { - this.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(uniform); } } } + 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/index.js b/src/core/index.js index 4f414e8..9e4f1f1 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -45,6 +45,7 @@ export { default as Quad } from './renderers/webgl/utils/Quad'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; +export { default as UniformGroup } from './shader/UniformGroup'; export { default as SpriteMaskFilter } from './renderers/webgl/filters/spriteMask/SpriteMaskFilter'; export { default as Filter } from './renderers/webgl/filters/Filter'; export { default as Application } from './Application'; diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js index b4fdfc9..290d4ba 100644 --- a/src/core/renderers/webgl/ShaderManager.js +++ b/src/core/renderers/webgl/ShaderManager.js @@ -1,5 +1,8 @@ import { GLShader } from 'pixi-gl-core'; import { PRECISION } from '../../const'; +import generateUniformsSync from '../../shader/generateUniformsSync2'; + +let UID = 0; /** * Helper class to create a webGL Texture @@ -29,6 +32,8 @@ this.gl = renderer.gl; this.shader = null; + + this.id = UID++; } /** @@ -71,6 +76,38 @@ 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.uniformD1ata, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } /** * Returns the underlying GLShade rof the currently bound shader. * This can be handy for when you to have a little more control over the setting of your uniforms. diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index 3768f03..b05379a 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -1,4 +1,5 @@ import Program from './Program'; +import UniformGroup from './UniformGroup'; // let math = require('../../../math'); /** @@ -15,26 +16,78 @@ constructor(program, uniforms) { this.program = program; - this.uniforms = uniforms || {}; + + // lets see whats been passed in + // uniforms should be converted to a uniform group + if(uniforms) + { + if(uniforms instanceof UniformGroup) + { + this.uniformGroup = uniforms; + } + else + { + this.uniformGroup = new UniformGroup(uniforms) + } + } + else + { + this.uniformGroup = new UniformGroup({}); + } // time to build some getters and setters! // I guess down the line this could sort of generate an instruction list rather than use dirty ids? // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniforms[i]; + const uniform = this.uniformGroup.uniforms[i]; - if (!uniform) + if (!uniform) // bad as we need check wher ethe uniforms are.. { - this.uniforms[i] = program.uniformData[i].value; + //this.uniforms.uniforms[i] = program.uniformData[i].value; } else if (uniform instanceof Array) { - this.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(uniform); } } } + 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 new file mode 100644 index 0000000..5de1a7a --- /dev/null +++ b/src/core/shader/UniformGroup.js @@ -0,0 +1,41 @@ +import Program from './Program'; +import generateUniformsSync from './generateUniformsSync'; + +let UID = 0; + +// let math = require('../../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.UniformGroup + */ +class UniformGroup +{ + /** + * @param {PIXI.Program} [program] - The program the shader will use. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(uniforms, _static) + { + this.uniforms = uniforms; + this.group = true; + // lets generate this when the shader ? + this.syncUniforms = {}; + this.dirtyId = 0; + this.id = UID++; + + this.static = !!_static; + } + + update() + { + this.dirtyId++; + } + + static from(uniforms, _static) + { + return new UniformGroup(uniforms, _static); + } +} + +export default UniformGroup; diff --git a/src/core/index.js b/src/core/index.js index 4f414e8..9e4f1f1 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -45,6 +45,7 @@ export { default as Quad } from './renderers/webgl/utils/Quad'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; +export { default as UniformGroup } from './shader/UniformGroup'; export { default as SpriteMaskFilter } from './renderers/webgl/filters/spriteMask/SpriteMaskFilter'; export { default as Filter } from './renderers/webgl/filters/Filter'; export { default as Application } from './Application'; diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js index b4fdfc9..290d4ba 100644 --- a/src/core/renderers/webgl/ShaderManager.js +++ b/src/core/renderers/webgl/ShaderManager.js @@ -1,5 +1,8 @@ import { GLShader } from 'pixi-gl-core'; import { PRECISION } from '../../const'; +import generateUniformsSync from '../../shader/generateUniformsSync2'; + +let UID = 0; /** * Helper class to create a webGL Texture @@ -29,6 +32,8 @@ this.gl = renderer.gl; this.shader = null; + + this.id = UID++; } /** @@ -71,6 +76,38 @@ 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.uniformD1ata, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } /** * Returns the underlying GLShade rof the currently bound shader. * This can be handy for when you to have a little more control over the setting of your uniforms. diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index 3768f03..b05379a 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -1,4 +1,5 @@ import Program from './Program'; +import UniformGroup from './UniformGroup'; // let math = require('../../../math'); /** @@ -15,26 +16,78 @@ constructor(program, uniforms) { this.program = program; - this.uniforms = uniforms || {}; + + // lets see whats been passed in + // uniforms should be converted to a uniform group + if(uniforms) + { + if(uniforms instanceof UniformGroup) + { + this.uniformGroup = uniforms; + } + else + { + this.uniformGroup = new UniformGroup(uniforms) + } + } + else + { + this.uniformGroup = new UniformGroup({}); + } // time to build some getters and setters! // I guess down the line this could sort of generate an instruction list rather than use dirty ids? // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniforms[i]; + const uniform = this.uniformGroup.uniforms[i]; - if (!uniform) + if (!uniform) // bad as we need check wher ethe uniforms are.. { - this.uniforms[i] = program.uniformData[i].value; + //this.uniforms.uniforms[i] = program.uniformData[i].value; } else if (uniform instanceof Array) { - this.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(uniform); } } } + 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 new file mode 100644 index 0000000..5de1a7a --- /dev/null +++ b/src/core/shader/UniformGroup.js @@ -0,0 +1,41 @@ +import Program from './Program'; +import generateUniformsSync from './generateUniformsSync'; + +let UID = 0; + +// let math = require('../../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.UniformGroup + */ +class UniformGroup +{ + /** + * @param {PIXI.Program} [program] - The program the shader will use. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(uniforms, _static) + { + this.uniforms = uniforms; + this.group = true; + // lets generate this when the shader ? + this.syncUniforms = {}; + this.dirtyId = 0; + this.id = UID++; + + this.static = !!_static; + } + + update() + { + this.dirtyId++; + } + + static from(uniforms, _static) + { + return new UniformGroup(uniforms, _static); + } +} + +export default UniformGroup; diff --git a/src/core/shader/generateUniformsSync2.js b/src/core/shader/generateUniformsSync2.js new file mode 100644 index 0000000..5a1da3b --- /dev/null +++ b/src/core/shader/generateUniformsSync2.js @@ -0,0 +1,165 @@ +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: '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: 'uniform1i(location, value)', +}; + +export default function generateUniformsSync2(group, uniformData) +{ + let textureCount = 1; + 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.shaderManager.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.size === 1) + { + func += `\nif (uniformValues.${i}.baseTexture) +{ + 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 +{ + uniformData.${i}.value = ${textureCount}; + renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; + gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + + uniformValues.${i}.bind(); +}`; + } + 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/index.js b/src/core/index.js index 4f414e8..9e4f1f1 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -45,6 +45,7 @@ export { default as Quad } from './renderers/webgl/utils/Quad'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; +export { default as UniformGroup } from './shader/UniformGroup'; export { default as SpriteMaskFilter } from './renderers/webgl/filters/spriteMask/SpriteMaskFilter'; export { default as Filter } from './renderers/webgl/filters/Filter'; export { default as Application } from './Application'; diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js index b4fdfc9..290d4ba 100644 --- a/src/core/renderers/webgl/ShaderManager.js +++ b/src/core/renderers/webgl/ShaderManager.js @@ -1,5 +1,8 @@ import { GLShader } from 'pixi-gl-core'; import { PRECISION } from '../../const'; +import generateUniformsSync from '../../shader/generateUniformsSync2'; + +let UID = 0; /** * Helper class to create a webGL Texture @@ -29,6 +32,8 @@ this.gl = renderer.gl; this.shader = null; + + this.id = UID++; } /** @@ -71,6 +76,38 @@ 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.uniformD1ata, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } /** * Returns the underlying GLShade rof the currently bound shader. * This can be handy for when you to have a little more control over the setting of your uniforms. diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index 3768f03..b05379a 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -1,4 +1,5 @@ import Program from './Program'; +import UniformGroup from './UniformGroup'; // let math = require('../../../math'); /** @@ -15,26 +16,78 @@ constructor(program, uniforms) { this.program = program; - this.uniforms = uniforms || {}; + + // lets see whats been passed in + // uniforms should be converted to a uniform group + if(uniforms) + { + if(uniforms instanceof UniformGroup) + { + this.uniformGroup = uniforms; + } + else + { + this.uniformGroup = new UniformGroup(uniforms) + } + } + else + { + this.uniformGroup = new UniformGroup({}); + } // time to build some getters and setters! // I guess down the line this could sort of generate an instruction list rather than use dirty ids? // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniforms[i]; + const uniform = this.uniformGroup.uniforms[i]; - if (!uniform) + if (!uniform) // bad as we need check wher ethe uniforms are.. { - this.uniforms[i] = program.uniformData[i].value; + //this.uniforms.uniforms[i] = program.uniformData[i].value; } else if (uniform instanceof Array) { - this.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(uniform); } } } + 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 new file mode 100644 index 0000000..5de1a7a --- /dev/null +++ b/src/core/shader/UniformGroup.js @@ -0,0 +1,41 @@ +import Program from './Program'; +import generateUniformsSync from './generateUniformsSync'; + +let UID = 0; + +// let math = require('../../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.UniformGroup + */ +class UniformGroup +{ + /** + * @param {PIXI.Program} [program] - The program the shader will use. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(uniforms, _static) + { + this.uniforms = uniforms; + this.group = true; + // lets generate this when the shader ? + this.syncUniforms = {}; + this.dirtyId = 0; + this.id = UID++; + + this.static = !!_static; + } + + update() + { + this.dirtyId++; + } + + static from(uniforms, _static) + { + return new UniformGroup(uniforms, _static); + } +} + +export default UniformGroup; diff --git a/src/core/shader/generateUniformsSync2.js b/src/core/shader/generateUniformsSync2.js new file mode 100644 index 0000000..5a1da3b --- /dev/null +++ b/src/core/shader/generateUniformsSync2.js @@ -0,0 +1,165 @@ +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: '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: 'uniform1i(location, value)', +}; + +export default function generateUniformsSync2(group, uniformData) +{ + let textureCount = 1; + 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.shaderManager.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.size === 1) + { + func += `\nif (uniformValues.${i}.baseTexture) +{ + 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 +{ + uniformData.${i}.value = ${textureCount}; + renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; + gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + + uniformValues.${i}.bind(); +}`; + } + 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/mesh/geometry/Attribute.js b/src/mesh/geometry/Attribute.js index 633f9bb..83f92c2 100644 --- a/src/mesh/geometry/Attribute.js +++ b/src/mesh/geometry/Attribute.js @@ -18,7 +18,7 @@ * @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) + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) { this.buffer = buffer; this.size = size; @@ -26,6 +26,7 @@ this.type = type; this.stride = stride; this.start = start; + this.instance = instance; } /** diff --git a/src/core/index.js b/src/core/index.js index 4f414e8..9e4f1f1 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -45,6 +45,7 @@ export { default as Quad } from './renderers/webgl/utils/Quad'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; +export { default as UniformGroup } from './shader/UniformGroup'; export { default as SpriteMaskFilter } from './renderers/webgl/filters/spriteMask/SpriteMaskFilter'; export { default as Filter } from './renderers/webgl/filters/Filter'; export { default as Application } from './Application'; diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js index b4fdfc9..290d4ba 100644 --- a/src/core/renderers/webgl/ShaderManager.js +++ b/src/core/renderers/webgl/ShaderManager.js @@ -1,5 +1,8 @@ import { GLShader } from 'pixi-gl-core'; import { PRECISION } from '../../const'; +import generateUniformsSync from '../../shader/generateUniformsSync2'; + +let UID = 0; /** * Helper class to create a webGL Texture @@ -29,6 +32,8 @@ this.gl = renderer.gl; this.shader = null; + + this.id = UID++; } /** @@ -71,6 +76,38 @@ 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.uniformD1ata, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } /** * Returns the underlying GLShade rof the currently bound shader. * This can be handy for when you to have a little more control over the setting of your uniforms. diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index 3768f03..b05379a 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -1,4 +1,5 @@ import Program from './Program'; +import UniformGroup from './UniformGroup'; // let math = require('../../../math'); /** @@ -15,26 +16,78 @@ constructor(program, uniforms) { this.program = program; - this.uniforms = uniforms || {}; + + // lets see whats been passed in + // uniforms should be converted to a uniform group + if(uniforms) + { + if(uniforms instanceof UniformGroup) + { + this.uniformGroup = uniforms; + } + else + { + this.uniformGroup = new UniformGroup(uniforms) + } + } + else + { + this.uniformGroup = new UniformGroup({}); + } // time to build some getters and setters! // I guess down the line this could sort of generate an instruction list rather than use dirty ids? // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniforms[i]; + const uniform = this.uniformGroup.uniforms[i]; - if (!uniform) + if (!uniform) // bad as we need check wher ethe uniforms are.. { - this.uniforms[i] = program.uniformData[i].value; + //this.uniforms.uniforms[i] = program.uniformData[i].value; } else if (uniform instanceof Array) { - this.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(uniform); } } } + 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 new file mode 100644 index 0000000..5de1a7a --- /dev/null +++ b/src/core/shader/UniformGroup.js @@ -0,0 +1,41 @@ +import Program from './Program'; +import generateUniformsSync from './generateUniformsSync'; + +let UID = 0; + +// let math = require('../../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.UniformGroup + */ +class UniformGroup +{ + /** + * @param {PIXI.Program} [program] - The program the shader will use. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(uniforms, _static) + { + this.uniforms = uniforms; + this.group = true; + // lets generate this when the shader ? + this.syncUniforms = {}; + this.dirtyId = 0; + this.id = UID++; + + this.static = !!_static; + } + + update() + { + this.dirtyId++; + } + + static from(uniforms, _static) + { + return new UniformGroup(uniforms, _static); + } +} + +export default UniformGroup; diff --git a/src/core/shader/generateUniformsSync2.js b/src/core/shader/generateUniformsSync2.js new file mode 100644 index 0000000..5a1da3b --- /dev/null +++ b/src/core/shader/generateUniformsSync2.js @@ -0,0 +1,165 @@ +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: '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: 'uniform1i(location, value)', +}; + +export default function generateUniformsSync2(group, uniformData) +{ + let textureCount = 1; + 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.shaderManager.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.size === 1) + { + func += `\nif (uniformValues.${i}.baseTexture) +{ + 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 +{ + uniformData.${i}.value = ${textureCount}; + renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; + gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + + uniformValues.${i}.bind(); +}`; + } + 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/mesh/geometry/Attribute.js b/src/mesh/geometry/Attribute.js index 633f9bb..83f92c2 100644 --- a/src/mesh/geometry/Attribute.js +++ b/src/mesh/geometry/Attribute.js @@ -18,7 +18,7 @@ * @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) + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) { this.buffer = buffer; this.size = size; @@ -26,6 +26,7 @@ this.type = type; this.stride = stride; this.start = start; + this.instance = instance; } /** diff --git a/src/mesh/geometry/Geometry.js b/src/mesh/geometry/Geometry.js index 1bfe793..c773eac 100644 --- a/src/mesh/geometry/Geometry.js +++ b/src/mesh/geometry/Geometry.js @@ -75,7 +75,7 @@ * * @return {PIXI.mesh.Geometry} returns self, useful for chaining. */ - addAttribute(id, buffer, size, normalised = false, type, stride, start) + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) { if (!buffer) { @@ -114,7 +114,7 @@ bufferIndex = this.buffers.length - 1; } - this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start); + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); return this; } @@ -269,7 +269,8 @@ attrib.normalized, attrib.type, attrib.stride, - attrib.start + attrib.start, + attrib.instance ); } diff --git a/src/core/index.js b/src/core/index.js index 4f414e8..9e4f1f1 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -45,6 +45,7 @@ export { default as Quad } from './renderers/webgl/utils/Quad'; export { default as Shader } from './shader/Shader'; export { default as Program } from './shader/Program'; +export { default as UniformGroup } from './shader/UniformGroup'; export { default as SpriteMaskFilter } from './renderers/webgl/filters/spriteMask/SpriteMaskFilter'; export { default as Filter } from './renderers/webgl/filters/Filter'; export { default as Application } from './Application'; diff --git a/src/core/renderers/webgl/ShaderManager.js b/src/core/renderers/webgl/ShaderManager.js index b4fdfc9..290d4ba 100644 --- a/src/core/renderers/webgl/ShaderManager.js +++ b/src/core/renderers/webgl/ShaderManager.js @@ -1,5 +1,8 @@ import { GLShader } from 'pixi-gl-core'; import { PRECISION } from '../../const'; +import generateUniformsSync from '../../shader/generateUniformsSync2'; + +let UID = 0; /** * Helper class to create a webGL Texture @@ -29,6 +32,8 @@ this.gl = renderer.gl; this.shader = null; + + this.id = UID++; } /** @@ -71,6 +76,38 @@ 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.uniformD1ata, group.uniforms, this.renderer); + + } + + syncUniformGroup(group) + { + const glShader = this.getGLShader(); + + if(!group.static || group.dirtyId !== glShader.uniformGroups[group.id]) + { + glShader.uniformGroups[group.id] = group.dirtyId; + const syncFunc = group.syncUniforms[this.shader.program.id] || this.createSynGroups(group); + + syncFunc(glShader.uniformData, group.uniforms, this.renderer); + } + + } + + createSynGroups(group) + { + group.syncUniforms[this.shader.program.id] = generateUniformsSync(group, this.shader.program.uniformData); + + return group.syncUniforms[this.shader.program.id]; + } /** * Returns the underlying GLShade rof the currently bound shader. * This can be handy for when you to have a little more control over the setting of your uniforms. diff --git a/src/core/shader/Shader.js b/src/core/shader/Shader.js index 3768f03..b05379a 100644 --- a/src/core/shader/Shader.js +++ b/src/core/shader/Shader.js @@ -1,4 +1,5 @@ import Program from './Program'; +import UniformGroup from './UniformGroup'; // let math = require('../../../math'); /** @@ -15,26 +16,78 @@ constructor(program, uniforms) { this.program = program; - this.uniforms = uniforms || {}; + + // lets see whats been passed in + // uniforms should be converted to a uniform group + if(uniforms) + { + if(uniforms instanceof UniformGroup) + { + this.uniformGroup = uniforms; + } + else + { + this.uniformGroup = new UniformGroup(uniforms) + } + } + else + { + this.uniformGroup = new UniformGroup({}); + } // time to build some getters and setters! // I guess down the line this could sort of generate an instruction list rather than use dirty ids? // does the trick for now though! for (const i in program.uniformData) { - const uniform = this.uniforms[i]; + const uniform = this.uniformGroup.uniforms[i]; - if (!uniform) + if (!uniform) // bad as we need check wher ethe uniforms are.. { - this.uniforms[i] = program.uniformData[i].value; + //this.uniforms.uniforms[i] = program.uniformData[i].value; } else if (uniform instanceof Array) { - this.uniforms[i] = new Float32Array(uniform); + this.uniformGroup.uniforms[i] = new Float32Array(uniform); } } } + 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 new file mode 100644 index 0000000..5de1a7a --- /dev/null +++ b/src/core/shader/UniformGroup.js @@ -0,0 +1,41 @@ +import Program from './Program'; +import generateUniformsSync from './generateUniformsSync'; + +let UID = 0; + +// let math = require('../../../math'); +/** + * @class + * @memberof PIXI + * @extends PIXI.UniformGroup + */ +class UniformGroup +{ + /** + * @param {PIXI.Program} [program] - The program the shader will use. + * @param {object} [uniforms] - Custom uniforms to use to augment the built-in ones. + */ + constructor(uniforms, _static) + { + this.uniforms = uniforms; + this.group = true; + // lets generate this when the shader ? + this.syncUniforms = {}; + this.dirtyId = 0; + this.id = UID++; + + this.static = !!_static; + } + + update() + { + this.dirtyId++; + } + + static from(uniforms, _static) + { + return new UniformGroup(uniforms, _static); + } +} + +export default UniformGroup; diff --git a/src/core/shader/generateUniformsSync2.js b/src/core/shader/generateUniformsSync2.js new file mode 100644 index 0000000..5a1da3b --- /dev/null +++ b/src/core/shader/generateUniformsSync2.js @@ -0,0 +1,165 @@ +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: '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: 'uniform1i(location, value)', +}; + +export default function generateUniformsSync2(group, uniformData) +{ + let textureCount = 1; + 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.shaderManager.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.size === 1) + { + func += `\nif (uniformValues.${i}.baseTexture) +{ + 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 +{ + uniformData.${i}.value = ${textureCount}; + renderer.boundTextures[${textureCount}] = renderer.emptyTextures[${textureCount}]; + gl.activeTexture(gl.TEXTURE0 + ${textureCount++}); + + uniformValues.${i}.bind(); +}`; + } + 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/mesh/geometry/Attribute.js b/src/mesh/geometry/Attribute.js index 633f9bb..83f92c2 100644 --- a/src/mesh/geometry/Attribute.js +++ b/src/mesh/geometry/Attribute.js @@ -18,7 +18,7 @@ * @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) + constructor(buffer, size, normalised = false, type = 5126, stride, start, instance) { this.buffer = buffer; this.size = size; @@ -26,6 +26,7 @@ this.type = type; this.stride = stride; this.start = start; + this.instance = instance; } /** diff --git a/src/mesh/geometry/Geometry.js b/src/mesh/geometry/Geometry.js index 1bfe793..c773eac 100644 --- a/src/mesh/geometry/Geometry.js +++ b/src/mesh/geometry/Geometry.js @@ -75,7 +75,7 @@ * * @return {PIXI.mesh.Geometry} returns self, useful for chaining. */ - addAttribute(id, buffer, size, normalised = false, type, stride, start) + addAttribute(id, buffer, size, normalised = false, type, stride, start, instance = false) { if (!buffer) { @@ -114,7 +114,7 @@ bufferIndex = this.buffers.length - 1; } - this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start); + this.attributes[id] = new Attribute(bufferIndex, size, normalised, type, stride, start, instance); return this; } @@ -269,7 +269,8 @@ attrib.normalized, attrib.type, attrib.stride, - attrib.start + attrib.start, + attrib.instance ); } diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index eaa0279..53be712 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -54,16 +54,15 @@ } // set unifomrs.. - this.renderer.shaderManager.setUniforms(mesh.shader.uniforms); + this.renderer.shaderManager.syncUniformGroup(mesh.shader.uniformGroup); // sync uniforms.. this.renderer.state.setState(mesh.state); // bind the geometry... this.bindGeometry(mesh.geometry, glShader); - // then render it - mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start); + mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } /** @@ -72,7 +71,7 @@ */ draw(mesh) { - mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start); + mesh.geometry.glVertexArrayObjects[this.CONTEXT_UID].draw(mesh.drawMode, mesh.size, mesh.start, mesh.geometry.instanceCount); } /** @@ -203,7 +202,8 @@ attribute.type || 5126, // (5126 = FLOAT) attribute.normalized, attribute.stride, - attribute.start); + attribute.start, + attribute.instance); } geometry.glVertexArrayObjects[this.CONTEXT_UID] = vao;