Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache shaders #11

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 1 addition & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,6 @@ Deletes the shader program and associated resources.
### `gl`
The WebGL context associated to the shader

### `handle`
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:

Expand Down Expand Up @@ -107,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.
Expand All @@ -129,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
113 changes: 85 additions & 28 deletions lib/create-attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,95 @@

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
, wrapper
, index
, locations
, dimension) {
this._gl = gl
this._wrapper = wrapper
this._index = index
this._locations = locations
this._dimension = dimension
this._name = name
this._constFunc = constFunc
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)
this._relink()
if(v !== this._locations[this._index]) {
this._locations[this._index] = v
this._wrapper.program = null
}
}
})


//Adds a vector attribute to obj
function addVectorAttribute(gl, program, location, dimension, obj, name, doLink) {
function addVectorAttribute(
gl
, wrapper
, index
, locations
, dimension
, obj
, name) {

//Construct constant function
var constFuncArgs = [ 'gl', 'v' ]
var varNames = []
for(var i=0; i<dimension; ++i) {
constFuncArgs.push('x'+i)
varNames.push('x'+i)
}
constFuncArgs.push([
'if(x0.length===void 0){return gl.vertexAttrib', dimension, 'f(v,', varNames.join(), ')}else{return gl.vertexAttrib', dimension, 'fv(v,x0)}'
'if(x0.length===void 0){return gl.vertexAttrib'
, dimension, 'f(v,'
, varNames.join()
, ')}else{return gl.vertexAttrib'
, dimension
, 'fv(v,x0)}'
].join(''))
var constFunc = Function.apply(undefined, constFuncArgs)
var attr = new ShaderAttribute(gl, program, location, dimension, name, constFunc, doLink)
var constFunc = Function.apply(null, constFuncArgs)

//Create attribute wrapper
var attr = new ShaderAttribute(
gl
, wrapper
, index
, locations
, dimension)

//Create accessor
Object.defineProperty(obj, name, {
set: function(x) {
gl.disableVertexAttribArray(attr._location)
constFunc(gl, attr._location, x)
gl.disableVertexAttribArray(locations[index])
constFunc(gl, locations[index], x)
return x
}
, get: function() {
Expand All @@ -63,19 +101,31 @@ function addVectorAttribute(gl, program, location, dimension, obj, name, doLink)
}

//Create shims for attributes
function createAttributeWrapper(gl, program, attributes, doLink) {
function createAttributeWrapper(
gl
, wrapper
, attributes
, locations) {

var obj = {}
for(var i=0, n=attributes.length; i<n; ++i) {

var a = attributes[i]
var name = a.name
var type = a.type
var location = gl.getAttribLocation(program, name)

switch(type) {
case 'bool':
case 'int':
case 'float':
addVectorAttribute(gl, program, location, 1, obj, name, doLink)
addVectorAttribute(
gl
, wrapper
, i
, locations
, 1
, obj
, name)
break

default:
Expand All @@ -84,7 +134,14 @@ function createAttributeWrapper(gl, program, attributes, doLink) {
if(d < 2 || d > 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)
} else {
throw new Error('gl-shader: Unknown data type for attribute ' + name + ': ' + type)
}
Expand Down
15 changes: 9 additions & 6 deletions lib/create-uniforms.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
129 changes: 129 additions & 0 deletions lib/shader-cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
'use strict'

exports.shader = getShaderReference
exports.program = createProgram

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<n; ++i) {
var p = cache.programs[programs[i]]
if(p) {
delete cache.programs[i]
gl.deleteProgram(p)
}
}

//Remove shader reference
gl.deleteShader(this.shader)
delete cache.shaders[(this.type === gl.FRAGMENT_SHADER)|0][this.src]
}
}

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.getShaderReference = function(type, src) {
var gl = this.gl
var shaders = this.shaders[(type === gl.FRAGMENT_SHADER)|0]
var shader = shaders[src]
if(!shader) {
var shaderObj = compileShader(gl, type, src)
shader = shaders[src] = new ShaderReference(
SHADER_COUNTER++,
src,
type,
shaderObj,
[],
1,
this)
} else {
shader.count += 1
}
return shader
}

function linkProgram(gl, vshader, fshader, attribs, locations) {
var program = gl.createProgram()
gl.attachShader(program, vshader)
gl.attachShader(program, fshader)
for(var i=0; i<attribs.length; ++i) {
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
}

proto.getProgram = function(vref, fref, attribs, locations) {
var token = [vref.id, fref.id, attribs.join(':'), locations.join(':')].join('@')
var prog = this.programs[token]
if(!prog) {
this.programs[token] = prog = linkProgram(
this.gl,
vref.shader,
fref.shader,
attribs,
locations)
vref.programs.push(token)
fref.programs.push(token)
}
return prog
}

function getCache(gl) {
var ctxCache = CACHE.get(gl)
if(!ctxCache) {
ctxCache = new ContextCache(gl)
CACHE.set(gl, ctxCache)
}
return ctxCache
}

function getShaderReference(gl, type, src) {
return getCache(gl).getShaderReference(type, src)
}

function createProgram(gl, vref, fref, attribs, locations) {
return getCache(gl).getProgram(vref, fref, attribs, locations)
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"url": "https://github.com/mikolalysenko/gl-shader-core/issues"
},
"dependencies": {
"dup": "^1.0.0"
"dup": "^1.0.0",
"weakmap-shim": "^1.1.0"
}
}
Loading