Created
April 24, 2017 23:27
-
-
Save mendes5/5c84aa33588513dc2aecc1df7329706f to your computer and use it in GitHub Desktop.
Copynpaste for webgl2 shader abstraction
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //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