Skip to content

Instantly share code, notes, and snippets.

@mendes5
Created April 24, 2017 23:27
Show Gist options
  • Select an option

  • Save mendes5/5c84aa33588513dc2aecc1df7329706f to your computer and use it in GitHub Desktop.

Select an option

Save mendes5/5c84aa33588513dc2aecc1df7329706f to your computer and use it in GitHub Desktop.
Copynpaste for webgl2 shader abstraction
//PROTOTYPE PROTOTYPE PROTOTYPE PROTOTYPE PROTOTYPE
function ShaderManager() {
const state = {
gl: canvas.getContext("webgl2"),
mode: null,
program: null,
arrayBuffer: null,
indexBuffer: null,
vertexArray: null,
attribute: null,
texute: null,
sampler: null,
draw() {
this.gl.drawArrays(this.mode, 0, state.arrayBuffer.data.length / state.arrayBuffer.size)
}
}
{
state.mode = state.gl.TRIANGLES
if (!state.gl) {
console.error('No webgl2 for you')
}
state.gl.viewport(0, 0, state.gl.canvas.width, state.gl.canvas.height)
state.gl.clearColor(0, 0, 0, 0)
state.gl.clear(state.gl.COLOR_BUFFER_BIT)
}
class Shader {
constructor(str) {
this.string = str;
this.typeEnum = /gl_Position/g.test(this.string)
? state.gl.VERTEX_SHADER
: state.gl.FRAGMENT_SHADER
this.shader = state.gl.createShader(this.typeEnum)
state.gl.shaderSource(this.shader, this.string)
state.gl.compileShader(this.shader)
this.infoLog = null
this.compileStatus = state.gl.getShaderParameter(this.shader, state.gl.COMPILE_STATUS)
if (!this.compileStatus) {
this.infoLog = state.gl.getShaderInfoLog(this.shader)
throw new Error(this.infoLog)
}
}
error(string, log) {
state.gl.deleteShader(this.shader)
this.shader = null
this.string = null
console.error(string, log)
}
}
class AttribManager {
constructor(caller, location, type) {
this.caller = caller
this.name = location
this.type = type
this.index = state.gl.getAttribLocation(this.caller.program, location)
this.info = state.gl.getActiveAttrib(this.caller.program, this.index)
let typeData = getDataOfType(type)
this.modeString = typeData.typeString
this.mode = typeData.typeEnum
this.size = typeData.size
this.enabled = false
this.initialized = false
this.bufferBound = null
}
setPointer() {
this.initialized = true
this.bufferBound = state.arrayBuffer
state.gl.vertexAttribPointer(this.index, this.size, this.mode, false, 0, 0)
}
enable() {
this.enabled = true
state.gl.enableVertexAttribArray(this.index)
}
disable() {
this.enabled = false
state.gl.disableVertexAttribArray(this.index)
}
}
class UniformArrayManager {
constructor(caller, location, size, type) {
this.caller = caller
this.location = location
this.type = type
this.arraySize = size
this.ePostFix = typeToEnum(this.type)
this.indexList = []
this.index = null
for (let i = 0; i < this.arraySize; i++) {
let subLocation = `${this.location}[${i}]`
let index = state.gl.getUniformLocation(this.caller.program, subLocation)
let inUse = !!index
let info = inUse ? state.gl.getActiveUniform(this.caller.program, index) : null
this.indexList.push({ index, info, subLocation, inUse })
}
this._setValue = setUniformFactory(this)
}
setValueOFIndex(index, v1, v2, v3, v4) {
const [a, b, c, d, e] = arguments
this.index = this.indexList[a].index
this._setValue(b, c, d, e)
}
set lastSet(a) {
console.warn('UniformManager.prototype.lastSet property is not used to be seted.')
}
get lastSet() {
return state.gl.getUniform(this.caller.program, this.index)
}
}
class UniformManger {
constructor(caller, location, type) {
this.caller = caller
this.location = location
this.type = type
this.ePostFix = typeToEnum(this.type)
this.index = state.gl.getUniformLocation(this.caller.program, this.location)
this.info = state.gl.getActiveUniform(this.caller.program, this.index)
this.setValue = setUniformFactory(this)
}
set lastSet(a) {
console.warn('UniformManager.prototype.lastSet property is not used to be seted.')
}
get lastSet() {
return state.gl.getUniform(this.caller.program, this.index)
}
}
class ShaderProgram {
constructor(vertex, fragment) {
this.vertexShader = new Shader(vertex)
this.fragmentShader = new Shader(fragment)
this.program = state.gl.createProgram()
state.gl.attachShader(this.program, this.vertexShader.shader)
state.gl.attachShader(this.program, this.fragmentShader.shader)
state.gl.linkProgram(this.program)
this.linkStatus = state.gl.getProgramParameter(this.program, state.gl.LINK_STATUS)
if (!this.linkStatus) {
this.infoLog = state.gl.getProgramInfoLog(this.program)
this.error('Falied to link the shader program (Maybe them have incorrect inputs/outputs/precision).', this.infoLog || 'No info log returned from the context')
}
this.uniforms = {}
this.uniformArrays = {}
this.attributes = {
enableAll() {
for (let att in this)
this[att] instanceof AttribManager && this[att].enable()
},
disableAll() {
for (let att in this)
this[att] instanceof AttribManager && this[att].disable()
}
}
this.getAttributes()
this.getUniforms()
this.validateStatus = 'not validated'
}
getAttributes() {
let data = this.vertexShader.string.match(/in(?![t]).*;/g)
data && data.map(item => {
let [_in, type, location] = item.split(' ')
location = location.slice(0, location.length - 1)
this.attributes[location] = new AttribManager(this, location, type)
})
}
getUniforms() {
const data1 = this.vertexShader.string.match(/uniform.*;/g)
const data2 = this.fragmentShader.string.match(/uniform.*;/g)
const data = data1.concat(data2)
data && data.map(item => {
let [useless, type, location] = item.split(' ')
location = location.slice(0, location.length - 1)
if (/([[].*])+/g.test(location)) {
const size = getTheDamnSizeOfTheUniform(location)
location = location.match(/([\w]+(?=[[]))/g)[0]
this.uniformArrays[location] = new UniformArrayManager(this, location, size, type)
} else {
this.uniforms[location] = new UniformManger(this, location, type)
}
})
}
use() {
state.gl.useProgram(this.program)
state.program = this
return this
}
error(string, log) {
state.gl.deleteProgram(this.program)
this.program = null
this.vertexShader.error('Forced to delete vertex shader', 'as the program has not created')
this.fragmentShader.error('Forced to delete fragment shader', 'as the program has not created')
console.error(log)
console.log(this)
throw new Error(string)
}
validate() {
state.gl.validateProgram(this.program)
this.validateStatus = state.gl.getProgramParameter(this.program, state.gl.VALIDATE_STATUS)
if (!this.validateStatus) {
this.infoLog = gl.getProgramInfoLog(this.program)
state.gl.deleteProgram(this.program)
this.program = null
console.error('The Program is not valid.', this.infoLog, this.program)
}
}
setUniform(location, v1, v2, v3, v4) {
this.uniformManger.setUniform(location, v1, v2, v3, v4)
}
setPointer(location) {
state.gl.vertexAttribPointer(this.vertextAttribs.attribLocations[location], this.vertextAttribs.attribSizes[location], state.gl.FLOAT, false, 0, 0)
}
}
class GL_Buffer {
constructor(type, name) {
this.stateName = name
this.type = state.gl[type]
this.autoUnbind = true
this.buffer = state.gl.createBuffer()
this.data = undefined
}
bind() {
state.gl.bindBuffer(this.type, this.buffer)
state[this.stateName] = this
return this
}
unbind() {
state.gl.bindBuffer(this.type, null)
state[this.stateName] = null
}
}
class GL_IndexBuffer extends GL_Buffer {
constructor(input, mode = true) {
super('ELEMENT_ARRAY_BUFFER', 'indexBuffer')
this.autoUnbind = mode
this.bind()
this.setData(input)
this.autoUnbind && this.unbind()
}
setData(data) {
this.data = null
this.data = new Uint16Array(data)
this.bind()
state.gl.bufferData(this.type, this.data, state.gl.STATIC_DRAW);
this.autoUnbind && this.unbind()
}
}
class GL_ArrayBuffer extends GL_Buffer {
constructor(input, size = 2, mode = true) {
super('ARRAY_BUFFER', 'arrayBuffer')
this.autoUnbind = mode
this.size = size
this.bind()
this.setData(input)
this.autoUnbind && this.unbind()
}
setData(data) {
this.data = null
this.data = new Float32Array(data)
this.bind()
state.gl.bufferData(this.type, this.data, state.gl.STATIC_DRAW);
this.autoUnbind && this.unbind()
}
}
class GL_VertexArrayObject {
constructor() {
this.vao = state.gl.createVertexArray()
}
bind() {
state.gl.bindVertexArray(this.vao)
state.vertexArray = this
return this
}
unbind() {
state.gl.bindVertexArray(null)
state.vertexArray = null
return this
}
draw() {
this.bind()
state.gl.drawArrays(state.gl.TRIANGLES, 0, state.arrayBuffer.data.length / state.arrayBuffer.size)
}
destroy() {
state.gl.deleteVertexArray()
}
}
class GL_Texture {
constructor(url) {
this.texture2D = state.gl.createTexture()
this.bind()
state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_MIN_FILTER, state.gl.LINEAR)
state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_MAG_FILTER, state.gl.LINEAR)
state.gl.texImage2D(state.gl.TEXTURE_2D, 0, state.gl.RGBA, 1, 1, 0, state.gl.RGBA, state.gl.UNSIGNED_BYTE, new Uint8Array([255, 255, 255, 255]))
this.textureBlob = new Image()
this.textureBlob.onload = _ => {
this.bind()
console.log(this.textureBlob)
state.gl.texImage2D(state.gl.TEXTURE_2D, 0, state.gl.RGBA, state.gl.RGBA, state.gl.UNSIGNED_BYTE, this.textureBlob)
state.gl.generateMipmap(3553)
state.gl.activeTexture(state.gl.TEXTURE0)
}
this.textureBlob.src = url
}
bind() {
state.gl.bindTexture(3553, this.texture2D)
state.gl.activeTexture(state.gl.TEXTURE0)
state.texture = this
return this
}
unbind() {
state.gl.bindTexture(3553, null)
state.texture = null
return this
}
destroy() {
this.unbind()
state.gl.deleteTexture(this.texture2D)
this.texture2D = null
this.textureBlob = null
}
}
class Quad {
constructor(r, url) {
this.vao = new GL_VertexArrayObject().bind()
this.program = new ShaderProgram(r.vertex, r.fragment).use()
this.program.attributes.enableAll()
this.texture = new GL_Texture(url).bind()
this.program.uniforms.u_texture.setValue(0)
this.buffer = new GL_ArrayBuffer([
0, 0, 100, 0, 0, 100, 0, 100, 100, 0, 100, 100,]).bind()
this.program.attributes.a_position.setPointer()
this.buffer2 = new GL_ArrayBuffer([0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]).bind()
this.program.attributes.a_uv.setPointer()
this.m0 = M3.fromProjection(state.gl.canvas.clientWidth, state.gl.canvas.clientHeight)
this.program.uniformArrays.u_matrix.setValueOFIndex(0, new Float32Array(this.m0))
this.m1 = M3.fromIdentity()
this.m2 = M3.fromIdentity()
this.m3 = M3.fromIdentity()
this.m4 = M3.fromTranslation(-50, -50)
this.program.uniformArrays.u_matrix.setValueOFIndex(4, new Float32Array(this.m4))
this.position = [0, 0]
this.scale = [1, 1]
this.x = 0
this.y = 0
this.rotation = 0
this.scaleX = 1
this.scaleY = 1
}
set x(x) {
this.program.uniformArrays.u_matrix.setValueOFIndex(1,
M3.translate(this.m1, this.position[0] = x, this.position[1])
)
}
set y(y) {
this.program.uniformArrays.u_matrix.setValueOFIndex(1,
M3.translate(this.m1, this.position[0], this.position[1] = y)
)
}
set rotation(a) {
this.program.uniformArrays.u_matrix.setValueOFIndex(2,
M3.rotate(this.m2, a)
)
}
set scaleX(x) {
this.program.uniformArrays.u_matrix.setValueOFIndex(3,
M3.scale(this.m3, this.scale[0] = x, this.scale[1])
)
}
set scaleY(y) {
this.program.uniformArrays.u_matrix.setValueOFIndex(3,
M3.scale(this.m3, this.scale[0], this.scale[1] = y)
)
}
update() {
this.vao.draw()
}
}
const getTheDamnSizeOfTheUniform = (location) => {
let meh = location.match(/([[].*])+/g)
meh = meh[0].match(/\d+/g)
meh = Number(meh[0])
return meh
}
const setUniformFactory = (caller) => {
let setValue;
let Gl_Set_Unifrom = state.gl[`uniform${caller.ePostFix}`].bind(state.gl)
switch (caller.ePostFix) {
case '1ui': case '1i': case '1f':
setValue = function (v) {
Gl_Set_Unifrom(this.index, v)
}
break
case '2ui': case '2i': case '2f':
setValue = function (v1, v2) {
Gl_Set_Unifrom(this.index, v1, v2)
}
break
case '3ui': case '3i': case '3f':
setValue = function (v1, v2, v3) {
Gl_Set_Unifrom(this.index, v1, v2, v3)
}
break
case '4ui': case '4i': case '4f':
setValue = function (v1, v2, v3, v4) {
Gl_Set_Unifrom(this.index, v1, v2, v3, v4)
}
break
case '1fv': case '2fv': case '3fv': case '4fv':
case '1uiv': case '2uiv': case '3uiv': case '4uiv':
case '1iv': case '2iv': case '3iv': case '4iv':
setValue = function (v) {
Gl_Set_Unifrom(this.index, v)
}
break
case 'Matrix2x3fv': case 'Matrix3fv': case 'Matrix4x3fv':
case 'Matrix2x4fv': case 'Matrix3x4fv': case 'Matrix4fv':
case 'Matrix2fv': case 'Matrix3x2fv': case 'Matrix4x2fv':
setValue = function (v) {
Gl_Set_Unifrom(this.index, false, v)
}
break
default:
throw new Error(`Unsupported uniform postfix ${ePostFix}.`)
break
}
return setValue.bind(caller)
}
const typeToEnum = (name) => {
let result = 0;
switch (name) {
case 'float': result = '1f'; break;
case 'int': result = '1i'; break;
case 'bool': result = '1i'; break;
case 'mat2': result = 'Matrix2fv'; break;
case 'mat3': result = 'Matrix3fv'; break;
case 'mat4': result = 'Matrix4fv'; break;
case 'mat2x3': result = 'Matrix2x3fv'; break;
case 'mat2x4': result = 'Matrix2x4fv'; break;
case 'mat3x2': result = 'Matrix3x2fv'; break;
case 'mat3x4': result = 'Matrix3x4fv'; break;
case 'mat4x2': result = 'Matrix4x2fv'; break;
case 'mat4x4': result = 'Matrix4x4fv'; break;
case 'vec2': result = '3fv'; break;
case 'vec3': result = '3fv'; break;
case 'vec4': result = '3fv'; break;
case 'ivec2': result = '2iv'; break;
case 'ivec3': result = '3iv'; break;
case 'ivec4': result = '4iv'; break;
case 'bvec2': result = '2iv'; break;
case 'bvec3': result = '3iv'; break;
case 'bvec4': result = '4iv'; break;
case 'uint': result = '1ui'; break;
case 'uvec2': result = '2uiv'; break;
case 'uvec3': result = '3uiv'; break;
case 'uvec4': result = '4uiv'; break;
case 'sampler2D': result = '1i'; break;
case 'sampler3D': result = '1i'; break;
case 'samplerCube': result = '1i'; break;
case 'samplerCubeShadow': result = '1i'; break;
case 'sampler2DShadow': result = '1i'; break;
case 'sampler2DArray': result = '1i'; break;
case 'sampler2DArrayShadow': result = '1i'; break;
case 'isampler2D': result = '1i'; break;
case 'isampler3D': result = '1i'; break;
case 'isamplerCube': result = '1i'; break;
case 'isampler2DArray': result = '1i'; break;
case 'usampler2D': result = '1i'; break;
case 'usampler3D': result = '1i'; break;
case 'usamplerCube': result = '1i'; break;
case 'usampler2DArray': result = '1i'; break;
}
return result
}
const getDataOfType = (str) => {
let result;
switch (str) {
case 'float': result = { size: 1, typeString: 'FLOAT', typeEnum: state.gl.FLOAT }; break;
case 'vec2': result = { size: 2, typeString: 'FLOAT', typeEnum: state.gl.FLOAT }; break;
case 'vec3': result = { size: 3, typeString: 'FLOAT', typeEnum: state.gl.FLOAT }; break;
case 'vec4': result = { size: 4, typeString: 'FLOAT', typeEnum: state.gl.FLOAT }; break;
case 'int': result = { size: 1, typeString: 'BYTE', typeEnum: state.gl.BYTE }; break;
case 'ivec2': result = { size: 2, typeString: 'BYTE', typeEnum: state.gl.BYTE }; break;
case 'ivec3': result = { size: 3, typeString: 'BYTE', typeEnum: state.gl.BYTE }; break;
case 'ivec4': result = { size: 4, typeString: 'BYTE', typeEnum: state.gl.BYTE }; break;
case 'uint': result = { size: 1, typeString: 'UNSIGNED_BYTE', typeEnum: state.gl.UNSIGNED_BYTE }; break;
case 'uvec2': result = { size: 2, typeString: 'UNSIGNED_BYTE', typeEnum: state.gl.UNSIGNED_BYTE }; break;
case 'uvec3': result = { size: 3, typeString: 'UNSIGNED_BYTE', typeEnum: state.gl.UNSIGNED_BYTE }; break;
case 'uvec4': result = { size: 4, typeString: 'UNSIGNED_BYTE', typeEnum: state.gl.UNSIGNED_BYTE }; break;
default: throw new Error('Unsupported attribute type ' + str); break;
}
return result;
}
return {
state: state,
fragmentShader: (src) => new FragmentShader(src),
vertexShader: (src) => new VertexShader(src),
shaderProgram: (vs, fs) => new ShaderProgram(vs, fs),
buffer: (inp, siz, mod) => new GL_Buffer(inp, siz, mod),
arrayBuffer: (inp, siz, mod) => new GL_ArrayBuffer(inp, siz, mod),
indexBuffer: (inp, mod) => new GL_IndexBuffer(inp, mod),
vertexArray: () => new GL_VertexArrayObject(),
texture: (url) => new GL_Texture(url),
quad: (p, i) => new Quad(p, i),
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment