3dexperiments/src/engine/shader.js

182 lines
4.4 KiB
JavaScript

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 }