import Resource from './resource' class Shader { constructor (type, source) { this.type = type this.source = source this.id = 0 } compile (gl) { const shader = gl.createShader(this.type) // Send the source to the shader object gl.shaderSource(shader, this.source) // Compile the shader program gl.compileShader(shader) // See if it compiled successfully if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { let inf = gl.getShaderInfoLog(shader) gl.deleteShader(shader) throw new Error('An error occurred compiling the shaders: ' + inf) } this.id = shader return this } } class ShaderProgram { constructor (name) { this.name = name this.id = 0 this.vertexShader = null this.geometryShader = null this.fragmentShader = null this.uniforms = {} this.attribs = {} } link (gl, vs, fs, gs) { let vsh = [ vs, fs, gs ] for (let i in vsh) { vsh[i] && vsh[i].compile(gl) } const shaderProgram = gl.createProgram() // Attach the shaders gl.attachShader(shaderProgram, vs.id) gs && gl.attachShader(shaderProgram, gs.id) gl.attachShader(shaderProgram, fs.id) // Link the program gl.linkProgram(shaderProgram) // If creating the shader program failed, error if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { throw new Error('Unable to initialize the shader program ' + this.name + ': ' + gl.getProgramInfoLog(shaderProgram)) } this.id = shaderProgram this.vertexShader = vs this.fragmentShader = fs this.geometryShader = gs // Detach shaders after use gl.detachShader(shaderProgram, vs.id) gs && gl.detachShader(shaderProgram, gs.id) gl.detachShader(shaderProgram, fs.id) return this } getUniformLocation (gl, name) { if (this.uniforms[name]) return this.uniforms[name] let uni = gl.getUniformLocation(this.id, name) if (uni < 0) return null this.uniforms[name] = uni return uni } getAttributeLocation (gl, name) { if (this.attribs[name]) return this.attribs[name] let pos = gl.getAttribLocation(this.id, name) if (pos < 0) throw new Error(`No such attribute location ${name} in shader ${this.name}!`) this.attribs[name] = pos return pos } hasAttribute (gl, name) { try { this.getAttributeLocation(gl, name) return true } catch (e) { return false } } setAttribute (gl, name, size, normalized, stride, offset, type) { let loc = this.getAttributeLocation(gl, name) // throws an error in case the name doesn't exist in shader gl.enableVertexAttribArray(loc) gl.vertexAttribPointer( loc, size, type || gl.FLOAT, normalized, stride, offset) } use (gl) { if (this.id === 0) return gl.useProgram(this.id) } } class ShaderManager { constructor () { this.shaders = {} } createShader (gl, name, vs, fs, gs) { if (this.shaders[name]) return this.shaders[name] let shader = new ShaderProgram(name) let vert = new Shader(gl.VERTEX_SHADER, vs) let frag = new Shader(gl.FRAGMENT_SHADER, fs) let geom = gs ? new Shader(gl.GEOMETRY_SHADER, gs) : null shader.link(gl, vert, frag, geom) this.shaders[name] = shader return shader } // Standard shader nomenclature: /assets/shaders/shader-name.vs|fs[|gs] // shader-name.vs and shader-name.fs are mandatory! createShaderFromFiles (gl, name, gs) { let stdloc = '/assets/shaders/' + name return new Promise((resolve, reject) => { function finishLink (vs, fs, gss) { try { let shader = this.createShader(gl, name, vs, fs, gss) resolve(shader) } catch (e) { reject(e) } } Resource.GET(stdloc + '.vs').then((vs) => { Resource.GET(stdloc + '.fs').then((fs) => { if (gs !== false) { // Try to find the geometry shader if it wasn't explicitly stated to not exist return Resource.GET(stdloc + '.gs').then((gss) => { finishLink.apply(this, [vs, fs, gss]) }, () => { finishLink.apply(this, [vs, fs]) }) } finishLink.apply(this, [vs, fs, null]) }, reject) }, reject) }) } use (gl, name) { if (this.shaders[name]) this.shaders[name].use(gl) } } export { Shader, ShaderProgram, ShaderManager }