docs: function createUniformWrapper(gl, program, 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)
    }
  
    function makePropSetter(path, index, type) {
      switch(type) {
        case 'bool':
        case 'int':
        case 'sampler2D':
        case 'samplerCube':
          return 'gl.uniform1i(locations[' + index + '],obj' + path + ')'
        case 'float':
          return 'gl.uniform1f(locations[' + index + '],obj' + path + ')'
        default:
          var vidx = type.indexOf('vec')
          if(0 <= vidx && vidx <= 1 && type.length === 4 + vidx) {
            var d = type.charCodeAt(type.length-1) - 48
            if(d < 2 || d > 4) {
              throw new Error('gl-shader: Invalid data type')
            }
            switch(type.charAt(0)) {
              case 'b':
              case 'i':
                return 'gl.uniform' + d + 'iv(locations[' + index + '],obj' + path + ')'
              case 'v':
                return 'gl.uniform' + d + 'fv(locations[' + index + '],obj' + path + ')'
              default:
                throw new Error('gl-shader: Unrecognized data type for vector ' + name + ': ' + type)
            }
          } else if(type.indexOf('mat') === 0 && type.length === 4) {
            var d = type.charCodeAt(type.length-1) - 48
            if(d < 2 || d > 4) {
              throw new Error('gl-shader: Invalid uniform dimension type for matrix ' + name + ': ' + type)
            }
            return 'gl.uniformMatrix' + d + 'fv(locations[' + index + '],false,obj' + path + ')'
          } else {
            throw new Error('gl-shader: Unknown uniform data type for ' + name + ': ' + type)
          }
        break
      }
    }
  
    function enumerateIndices(prefix, type) {
      if(typeof type !== 'object') {
        return [ [prefix, type] ]
      }
      var indices = []
      for(var id in type) {
        var prop = type[id]
        var tprefix = prefix
        if(parseInt(id) + '' === id) {
          tprefix += '[' + id + ']'
        } else {
          tprefix += '.' + id
        }
        if(typeof prop === 'object') {
          indices.push.apply(indices, enumerateIndices(tprefix, prop))
        } else {
          indices.push([tprefix, prop])
        }
      }
      return indices
    }
  
    function makeSetter(type) {
      var code = [ 'return function updateProperty(obj){' ]
      var indices = enumerateIndices('', type)
      for(var i=0; i<indices.length; ++i) {
        var item = indices[i]
        var path = item[0]
        var idx  = item[1]
        if(locations[idx]) {
          code.push(makePropSetter(path, idx, uniforms[idx].type))
        }
      }
      code.push('return obj}')
      var proc = new Function('gl', 'prog', 'locations', code.join('\n'))
      return proc(gl, program, locations)
    }
  
    function defaultValue(type) {
      switch(type) {
        case 'bool':
          return false
        case 'int':
        case 'sampler2D':
        case 'samplerCube':
          return 0
        case 'float':
          return 0.0
        default:
          var vidx = type.indexOf('vec')
          if(0 <= vidx && vidx <= 1 && type.length === 4 + vidx) {
            var d = type.charCodeAt(type.length-1) - 48
            if(d < 2 || d > 4) {
              throw new Error('gl-shader: Invalid data type')
            }
            if(type.charAt(0) === 'b') {
              return dup(d, false)
            }
            return dup(d)
          } else if(type.indexOf('mat') === 0 && type.length === 4) {
            var d = type.charCodeAt(type.length-1) - 48
            if(d < 2 || d > 4) {
              throw new Error('gl-shader: Invalid uniform dimension type for matrix ' + name + ': ' + type)
            }
            return dup([d,d])
          } else {
            throw new Error('gl-shader: Unknown uniform data type for ' + name + ': ' + type)
          }
        break
      }
    }
  
    function storeProperty(obj, prop, type) {
      if(typeof type === 'object') {
        var child = processObject(type)
        Object.defineProperty(obj, prop, {
          get: identity(child),
          set: makeSetter(type),
          enumerable: true,
          configurable: false
        })
      } else {
        if(locations[type]) {
          Object.defineProperty(obj, prop, {
            get: makeGetter(type),
            set: makeSetter(type),
            enumerable: true,
            configurable: false
          })
        } else {
          obj[prop] = defaultValue(uniforms[type].type)
        }
      }
    }
  
    function processObject(obj) {
      var result
      if(Array.isArray(obj)) {
        result = new Array(obj.length)
        for(var i=0; i<obj.length; ++i) {
          storeProperty(result, i, obj[i])
        }
      } else {
        result = {}
        for(var id in obj) {
          storeProperty(result, id, obj[id])
        }
      }
      return result
    }
  
    //Return data
    var coallesced = coallesceUniforms(uniforms, true)
    return {
      get: identity(processObject(coallesced)),
      set: makeSetter(coallesced),
      enumerable: true,
      configurable: true
    }
  }