From 8c17b9e5415464448c03be3f5410bf40b65bac37 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Sat, 15 Nov 2014 13:49:40 -0600 Subject: [PATCH 1/7] rewrite, use caching and fix attribute location bug --- lib/create-attributes.js | 115 ++++++++++++++++++------ lib/shader-cache.js | 72 +++++++++++++++ package.json | 3 +- shader-core.js | 186 ++++++++++++++++++++------------------- 4 files changed, 257 insertions(+), 119 deletions(-) create mode 100644 lib/shader-cache.js diff --git a/lib/create-attributes.js b/lib/create-attributes.js index 1806bbd..104827e 100644 --- a/lib/create-attributes.js +++ b/lib/create-attributes.js @@ -2,34 +2,49 @@ module.exports = createAttributeWrapper -//Shader attribute class -function ShaderAttribute(gl, program, location, dimension, name, constFunc, relink) { - this._gl = gl - this._program = program - this._location = location +function ShaderAttribute( + gl + , index + , locations + , dimension + , relink) { + this._gl = gl + this._index = index + this._locations = locations this._dimension = dimension - this._name = name - this._constFunc = constFunc - this._relink = relink + this._relink = relink } var proto = ShaderAttribute.prototype -proto.pointer = function setAttribPointer(type, normalized, stride, offset) { - var gl = this._gl - gl.vertexAttribPointer(this._location, this._dimension, type||gl.FLOAT, !!normalized, stride||0, offset||0) - this._gl.enableVertexAttribArray(this._location) +proto.pointer = function setAttribPointer( + type + , normalized + , stride + , offset) { + + var self = this + var gl = self._gl + var location = self._locations[self._index] + + gl.vertexAttribPointer( + location + , self._dimension + , type || gl.FLOAT + , !!normalized + , stride || 0 + , offset || 0) + + this._gl.enableVertexAttribArray(location) } Object.defineProperty(proto, 'location', { get: function() { - return this._location + return this._locations[this._index] } , set: function(v) { - if(v !== this._location) { - this._location = v - this._gl.bindAttribLocation(this._program, v, this._name) - this._gl.linkProgram(this._program) + if(v !== this._locations[this._index]) { + this._locations[this._index] = v this._relink() } } @@ -37,7 +52,17 @@ Object.defineProperty(proto, 'location', { //Adds a vector attribute to obj -function addVectorAttribute(gl, program, location, dimension, obj, name, doLink) { +function addVectorAttribute( + gl + , wrapper + , index + , locations + , dimension + , obj + , name + , relink) { + + //Construct constant function var constFuncArgs = [ 'gl', 'v' ] var varNames = [] for(var i=0; i 4) { throw new Error('gl-shader: Invalid data type for attribute ' + name + ': ' + type) } - addVectorAttribute(gl, program, location, d, obj, name, doLink) + addVectorAttribute( + gl + , wrapper + , i + , locations + , d + , obj + , name + , relink) } else { throw new Error('gl-shader: Unknown data type for attribute ' + name + ': ' + type) } diff --git a/lib/shader-cache.js b/lib/shader-cache.js new file mode 100644 index 0000000..2503ef6 --- /dev/null +++ b/lib/shader-cache.js @@ -0,0 +1,72 @@ +'use strict' + +module.exports = createProgram + +var weakMap = typeof WeakMap === 'undefined' ? require('weakmap') : WeakMap +var CACHE = new weakMap() + +function ContextCache(gl) { + this.gl = gl + this.shaders = [{}, {}] + this.programs = {} +} + +var proto = ContextCache.prototype + +function compileShader(gl, type, src) { + var shader = gl.createShader(type) + gl.shaderSource(shader, src) + gl.compileShader(shader) + if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + var errLog = gl.getShaderInfoLog(shader) + console.error('gl-shader: Error compiling shader:', errLog) + throw new Error('gl-shader: Error compiling shader:', errLog) + } + return shader +} + +proto.getShader = function(src, type) { + var gl = this.gl + var shaders = this.shaders[(type === gl.FRAGMENT_SHADER)|0] + var shader = shaders[src] + if(!shader) { + shaders[src] = shader = compileShader(gl, type, src) + } + return shader +} + +proto.linkProgram = function(vsrc, fsrc, attribs, locations) { + var gl = this.gl + var vshader = this.getShader(vsrc, gl.VERTEX_SHADER) + var fshader = this.getShader(fsrc, gl.FRAGMENT_SHADER) + var program = gl.createProgram() + gl.attachShader(program, vshader) + gl.attachShader(program, fshader) + for(var i=0; i Date: Sun, 16 Nov 2014 10:12:53 -0600 Subject: [PATCH 2/7] small bug fixes --- lib/create-uniforms.js | 15 +++++++++------ lib/shader-cache.js | 7 ++++++- shader-core.js | 27 +++++++++++++++------------ 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/lib/create-uniforms.js b/lib/create-uniforms.js index 8185354..fc82ee4 100644 --- a/lib/create-uniforms.js +++ b/lib/create-uniforms.js @@ -12,12 +12,15 @@ function identity(x) { } //Create shims for uniforms -function createUniformWrapper(gl, program, uniforms, locations) { +function createUniformWrapper(gl, wrapper, uniforms, locations) { function makeGetter(index) { - var proc = new Function('gl', 'prog', 'locations', - 'return function(){return gl.getUniform(prog,locations[' + index + '])}') - return proc(gl, program, locations) + var proc = new Function( + 'gl' + , 'wrapper' + , 'locations' + , 'return function(){return gl.getUniform(wrapper.program,locations[' + index + '])}') + return proc(gl, wrapper, locations) } function makePropSetter(path, index, type) { @@ -92,8 +95,8 @@ function createUniformWrapper(gl, program, uniforms, locations) { } } code.push('return obj}') - var proc = new Function('gl', 'prog', 'locations', code.join('\n')) - return proc(gl, program, locations) + var proc = new Function('gl', 'locations', code.join('\n')) + return proc(gl, locations) } function defaultValue(type) { diff --git a/lib/shader-cache.js b/lib/shader-cache.js index 2503ef6..74fa7cc 100644 --- a/lib/shader-cache.js +++ b/lib/shader-cache.js @@ -20,7 +20,7 @@ function compileShader(gl, type, src) { if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { var errLog = gl.getShaderInfoLog(shader) console.error('gl-shader: Error compiling shader:', errLog) - throw new Error('gl-shader: Error compiling shader:', errLog) + throw new Error('gl-shader: Error compiling shader:' + errLog) } return shader } @@ -46,6 +46,11 @@ proto.linkProgram = function(vsrc, fsrc, attribs, locations) { gl.bindAttribLocation(program, locations[i], attribs[i]) } gl.linkProgram(program) + if(!gl.getProgramParameter(program, gl.LINK_STATUS)) { + var errLog = gl.getProgramInfoLog(program) + console.error('gl-shader: Error linking program:', errLog) + throw new Error('gl-shader: Error linking program:' + errLog) + } return program } diff --git a/shader-core.js b/shader-core.js index 19b9191..a092c40 100644 --- a/shader-core.js +++ b/shader-core.js @@ -26,10 +26,13 @@ proto.bind = function() { } proto.dispose = function() { - //TODO: How to manage this? + //Now a no-op, maintained for backwards compatibility + + //Leaking shader objects is reasonable since they: // - //Leaking shader/progs seems reasonable since they are - //small resources and rebuilding them is expensive + // 1. have expensive construction + // 2. use a relatively small amount of GPU memory + // 3. could be used by user space programs in the future // } @@ -73,8 +76,8 @@ proto.updateExports = function( //Build program wrapper.program = createProgram( gl - , wrapper.vsrc - , wrapper.fsrc + , wrapper._vertSource + , wrapper._fragSource , attributeNames , attributeLocations) @@ -90,23 +93,23 @@ proto.updateExports = function( relink() //Generate type info - this.types = { + wrapper.types = { uniforms: makeReflect(uniforms), attributes: makeReflect(attributes) } //Generate attribute wrappers - this.attributes = createAttributeWrapper( + wrapper.attributes = createAttributeWrapper( gl - , program + , wrapper , attributes , attributeLocations , relink) //Generate uniform wrappers - Object.defineProperty(this, 'uniforms', createUniformWrapper( + Object.defineProperty(wrapper, 'uniforms', createUniformWrapper( gl - , program + , wrapper , uniforms , uniformLocations)) } @@ -122,8 +125,8 @@ function createShader( var shader = new Shader( gl - , vertShader - , fragShader + , vertSource + , fragSource , attributeLocations) shader.updateExports(uniforms, attributes) From e897cc5ba3c3e02ebcf8a6da2fa94eb8ce4a5d54 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Sun, 16 Nov 2014 10:17:14 -0600 Subject: [PATCH 3/7] doc updates --- README.md | 11 +++-------- shader-core.js | 3 +-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index f5ea1a9..4bd70fe 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The core of [gl-shader](https://github.com/mikolalysenko/gl-shader), without the ## API -### `var shader = require("gl-shader-core")(gl, vertexSource, fragmentSource, uniforms, attributes)` +### `var shader = require("gl-shader-core")(gl, vertexSource, fragmentSource, uniforms, attributes, attributeLocations)` Constructs a packaged gl-shader object with shims for all of the uniforms and attributes in the program. * `gl` is the webgl context in which the program will be created @@ -16,6 +16,7 @@ Constructs a packaged gl-shader object with shims for all of the uniforms and at * `fragmentSource` is the source code for the fragment shader * `uniforms` is a list of all uniforms exported by the shader program * `attributes` is a list of all attributes exported by the shader program +* `attributeLocations` is a list of default attribute locations The uniform and attributes variables have output which is consistent with [glsl-extract](https://npmjs.org/package/glsl-extract). @@ -36,15 +37,9 @@ Deletes the shader program and associated resources. ### `gl` The WebGL context associated to the shader -### `handle` +### `program` A handle to the underlying WebGL program object -### `vertexShader` -A handle to the underlying WebGL fragment shader object - -### `fragmentShader` -A handle to the underlying WebGL vertex shader object - ## Uniforms The uniforms for the shader program are packaged up as properties in the `shader.uniforms` object. For example, to update a scalar uniform you can just assign to it: diff --git a/shader-core.js b/shader-core.js index a092c40..0b01077 100644 --- a/shader-core.js +++ b/shader-core.js @@ -68,8 +68,7 @@ proto.updateExports = function( } else { attributeLocations = attributeLocations.slice() } - - + //Relinks all uniforms function relink() { From a03f5021f2b82ff77adb17cf546d1c345ad8a653 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Sun, 16 Nov 2014 11:40:51 -0600 Subject: [PATCH 4/7] defer linking to bind() after location change --- README.md | 3 -- lib/create-attributes.js | 24 ++++++------- lib/shader-cache.js | 2 +- shader-core.js | 73 +++++++++++++++++++++++++--------------- 4 files changed, 56 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 4bd70fe..3d595ec 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,6 @@ Deletes the shader program and associated resources. ### `gl` The WebGL context associated to the shader -### `program` -A handle to the underlying WebGL program object - ## Uniforms The uniforms for the shader program are packaged up as properties in the `shader.uniforms` object. For example, to update a scalar uniform you can just assign to it: diff --git a/lib/create-attributes.js b/lib/create-attributes.js index 104827e..1efb93a 100644 --- a/lib/create-attributes.js +++ b/lib/create-attributes.js @@ -4,15 +4,15 @@ module.exports = createAttributeWrapper function ShaderAttribute( gl + , wrapper , index , locations - , dimension - , relink) { + , dimension) { this._gl = gl + this._wrapper = wrapper this._index = index this._locations = locations this._dimension = dimension - this._relink = relink } var proto = ShaderAttribute.prototype @@ -45,7 +45,7 @@ Object.defineProperty(proto, 'location', { , set: function(v) { if(v !== this._locations[this._index]) { this._locations[this._index] = v - this._relink() + this._wrapper.program = null } } }) @@ -59,8 +59,7 @@ function addVectorAttribute( , locations , dimension , obj - , name - , relink) { + , name) { //Construct constant function var constFuncArgs = [ 'gl', 'v' ] @@ -82,10 +81,10 @@ function addVectorAttribute( //Create attribute wrapper var attr = new ShaderAttribute( gl + , wrapper , index , locations - , dimension - , relink) + , dimension) //Create accessor Object.defineProperty(obj, name, { @@ -106,8 +105,7 @@ function createAttributeWrapper( gl , wrapper , attributes - , locations - , relink) { + , locations) { var obj = {} for(var i=0, n=attributes.length; i b.name) { + return 1 + } + return 0 + }) + + //Extract names var attributeNames = attributes.map(function(attr) { return attr.name }) - //Read in attribute locations - if(!Array.isArray(attributeLocations) || - attributes.length !== attributeLocations.length) { - attributeLocations = defaultLocations(attributeNames) - } else { - attributeLocations = attributeLocations.slice() + //Get default location + var attributeLocations = attributes.map(function(attr) { + if('location' in attr) { + return attr.location|0 + } else { + return -1 + } + }) + + //For all unspecified attributes, assign them lexicographically min attribute + var curLocation = 0 + for(var i=0; i= 0) { + curLocation += 1 + } + attributeLocations[i] = curLocation + } } - + //Relinks all uniforms function relink() { @@ -88,9 +104,12 @@ proto.updateExports = function( } } - //Relink the program + //Perform initial linking relink() + //Save relinking procedure, defer until runtime + wrapper._relink = relink + //Generate type info wrapper.types = { uniforms: makeReflect(uniforms), @@ -119,17 +138,15 @@ function createShader( , vertSource , fragSource , uniforms - , attributes - , attributeLocations) { + , attributes) { var shader = new Shader( gl , vertSource - , fragSource - , attributeLocations) + , fragSource) shader.updateExports(uniforms, attributes) return shader } -module.exports = createShader +module.exports = createShader \ No newline at end of file From ef51ed4b236a62d0739cc89d4455e1a017bc7da2 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Sun, 16 Nov 2014 11:43:25 -0600 Subject: [PATCH 5/7] documentation fixes --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3d595ec..5aecd9d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The core of [gl-shader](https://github.com/mikolalysenko/gl-shader), without the ## API -### `var shader = require("gl-shader-core")(gl, vertexSource, fragmentSource, uniforms, attributes, attributeLocations)` +### `var shader = require("gl-shader-core")(gl, vertexSource, fragmentSource, uniforms, attributes)` Constructs a packaged gl-shader object with shims for all of the uniforms and attributes in the program. * `gl` is the webgl context in which the program will be created @@ -16,7 +16,6 @@ Constructs a packaged gl-shader object with shims for all of the uniforms and at * `fragmentSource` is the source code for the fragment shader * `uniforms` is a list of all uniforms exported by the shader program * `attributes` is a list of all attributes exported by the shader program -* `attributeLocations` is a list of default attribute locations The uniform and attributes variables have output which is consistent with [glsl-extract](https://npmjs.org/package/glsl-extract). @@ -99,9 +98,7 @@ Or you can read the currently bound location back by just accessing it: console.log(attrib.location) ``` -Internally, these methods just call [`gl.bindAttribLocation`](http://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindAttribLocation.xml) and access the stored location. - -**WARNING** Changing the attribute location requires recompiling the program. Do not dynamically modify this variable in your render loop. +**WARNING** Changing the attribute location requires recompiling the program. This recompilation is deferred until the next call to `.bind()` ### `attrib.pointer([type, normalized, stride, offset])` A shortcut for `gl.vertexAttribPointer`/`gl.enableVertexAttribArray`. See the [OpenGL man page for details on how this works](http://www.khronos.org/opengles/sdk/docs/man/xhtml/glVertexAttribPointer.xml). The main difference here is that the WebGL context, size and index are known and so these parameters are bound. @@ -121,6 +118,5 @@ console.log(shader.types) This reflects the uniform and attribute parameters that were passed to the shader constructor. - ## Credits (c) 2013 Mikola Lysenko. MIT License \ No newline at end of file From bd0936246b454df3c4865b1454449945808e8fe2 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 5 Dec 2014 16:03:33 -0600 Subject: [PATCH 6/7] added reference counting on shader objects, clean up on shader destruction --- lib/shader-cache.js | 82 ++++++++++++++++++++++++++++++++++++--------- package.json | 2 +- shader-core.js | 31 +++++++---------- 3 files changed, 81 insertions(+), 34 deletions(-) diff --git a/lib/shader-cache.js b/lib/shader-cache.js index d195f71..6e37123 100644 --- a/lib/shader-cache.js +++ b/lib/shader-cache.js @@ -1,10 +1,44 @@ 'use strict' -module.exports = createProgram +exports.shader = getShaderReference +exports.program = createProgram -var weakMap = typeof WeakMap === 'undefined' ? require('weakmap') : WeakMap +var weakMap = typeof WeakMap === 'undefined' ? require('weakmap-shim') : WeakMap var CACHE = new weakMap() +var SHADER_COUNTER = 0 + +function ShaderReference(id, src, type, shader, programs, count, cache) { + this.id = id + this.src = src + this.type = type + this.shader = shader + this.count = count + this.programs = [] + this.cache = cache +} + +ShaderReference.prototype.dispose = function() { + if(--this.count === 0) { + var cache = this.cache + var gl = cache.gl + + //Remove program references + var programs = this.programs + for(var i=0, n=programs.length; i Date: Fri, 5 Dec 2014 16:11:50 -0600 Subject: [PATCH 7/7] fix up a typo --- lib/shader-cache.js | 2 +- shader-core.js | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/shader-cache.js b/lib/shader-cache.js index 6e37123..45eb415 100644 --- a/lib/shader-cache.js +++ b/lib/shader-cache.js @@ -35,7 +35,7 @@ ShaderReference.prototype.dispose = function() { //Remove shader reference gl.deleteShader(this.shader) - delete cache.shaders[(type === gl.FRAGMENT_SHADER)|0][this.src] + delete cache.shaders[(this.type === gl.FRAGMENT_SHADER)|0][this.src] } } diff --git a/shader-core.js b/shader-core.js index 1b40834..20609ae 100644 --- a/shader-core.js +++ b/shader-core.js @@ -32,7 +32,10 @@ proto.bind = function() { proto.dispose = function() { this._fref.dispose() this._vref.dispose() - this._fref = this._vref = null + this.program = + this._relink = + this._fref = + this._vref = null } //Update export hook for glslify-live