diff options
Diffstat (limited to 'web/pw-frontend/src/lib/visualizer/webgl/shader.ts')
-rw-r--r-- | web/pw-frontend/src/lib/visualizer/webgl/shader.ts | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/web/pw-frontend/src/lib/visualizer/webgl/shader.ts b/web/pw-frontend/src/lib/visualizer/webgl/shader.ts new file mode 100644 index 0000000..942c4c2 --- /dev/null +++ b/web/pw-frontend/src/lib/visualizer/webgl/shader.ts @@ -0,0 +1,327 @@ +import type { Dictionary } from './util'; + +function error(msg: string) { + console.error(msg); +} + +const defaultShaderType = [ + "VERTEX_SHADER", + "FRAGMENT_SHADER" +]; + +/// Create Shader from Source string +function loadShader( + gl: WebGLRenderingContext, + shaderSource: string, + shaderType: number, + opt_errorCallback: any, +): WebGLShader { + var errFn = opt_errorCallback || error; + // Create the shader object + var shader = gl.createShader(shaderType); + + // Load the shader source + gl.shaderSource(shader, shaderSource); + + // Compile the shader + gl.compileShader(shader); + + // Check the compile status + var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); + if (!compiled) { + // Something went wrong during compilation; get the error + var lastError = gl.getShaderInfoLog(shader); + errFn("*** Error compiling shader '" + shader + "':" + lastError); + gl.deleteShader(shader); + return null; + } + + return shader; +} + +/// Actually Create Program with Shader's +function createProgram( + gl: WebGLRenderingContext, + shaders: WebGLShader[], + opt_attribs: string[], + opt_locations: number[], + opt_errorCallback: any, +): WebGLProgram { + var errFn = opt_errorCallback || error; + var program = gl.createProgram(); + shaders.forEach(function (shader) { + gl.attachShader(program, shader); + }); + if (opt_attribs) { + opt_attribs.forEach(function (attrib, ndx) { + gl.bindAttribLocation( + program, + opt_locations ? opt_locations[ndx] : ndx, + attrib); + }); + } + gl.linkProgram(program); + + // Check the link status + var linked = gl.getProgramParameter(program, gl.LINK_STATUS); + if (!linked) { + // something went wrong with the link + var lastError = gl.getProgramInfoLog(program); + errFn("Error in program linking:" + lastError); + + gl.deleteProgram(program); + return null; + } + return program; +} + +export class ShaderFactory { + frag_source: string; + vert_source: string; + + static async create_factory(frag_url: string, vert_url: string): Promise<ShaderFactory> { + const sources = await Promise.all([ + fetch(frag_url).then((r) => r.text()), + fetch(vert_url).then((r) => r.text()), + ]); + + return new ShaderFactory(sources[0], sources[1]); + } + + constructor(frag_source: string, vert_source: string ) { + this.frag_source = frag_source; + this.vert_source = vert_source; + } + + create_shader( + gl: WebGLRenderingContext, + context?: Dictionary<string>, + opt_attribs?: string[], + opt_locations?: number[], + opt_errorCallback?: any, + ): Shader { + let vert = this.vert_source.slice(); + let frag = this.frag_source.slice(); + for (let key in context) { + vert = vert.replace(new RegExp("\\$" + key, 'g'), context[key]); + frag = frag.replace(new RegExp("\\$" + key, 'g'), context[key]); + } + + const shaders = [ + loadShader(gl, vert, gl.VERTEX_SHADER, opt_errorCallback), + loadShader(gl, frag, gl.FRAGMENT_SHADER, opt_errorCallback), + ]; + + return new Shader(createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback)); + } +} + +export class Shader { + shader: WebGLProgram; + uniformCache: Dictionary<WebGLUniformLocation>; + attribCache: Dictionary<number>; + + static async createProgramFromUrls( + gl: WebGLRenderingContext, + vert_url: string, + frag_url: string, + context?: Dictionary<string>, + opt_attribs?: string[], + opt_locations?: number[], + opt_errorCallback?: any, + ): Promise<Shader> { + const sources = (await Promise.all([ + fetch(vert_url).then((r) => r.text()), + fetch(frag_url).then((r) => r.text()), + ])).map(x => { + for (let key in context) { + x = x.replace(new RegExp("\\$" + key, 'g'), context[key]); + } + return x; + }); + + const shaders = [ + loadShader(gl, sources[0], 35633, opt_errorCallback), + loadShader(gl, sources[1], 35632, opt_errorCallback), + ]; + return new Shader(createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback)); + } + + constructor(shader: WebGLProgram) { + this.shader = shader; + this.uniformCache = {}; + this.attribCache = {}; + } + + bind(gl: WebGLRenderingContext) { + gl.useProgram(this.shader); + } + + // Different locations have different types :/ + getUniformLocation(gl: WebGLRenderingContext, name: string): WebGLUniformLocation { + if (this.uniformCache[name] === undefined) { + this.uniformCache[name] = gl.getUniformLocation(this.shader, name); + } + + return this.uniformCache[name]; + } + + getAttribLocation(gl: WebGLRenderingContext, name: string): number { + if (this.attribCache[name] === undefined) { + this.attribCache[name] = gl.getAttribLocation(this.shader, name); + } + + return this.attribCache[name]; + } + + uniform<T extends Uniform>( + gl: WebGLRenderingContext, + name: string, + uniform: T, + ) { + this.bind(gl); + const location = this.getUniformLocation(gl, name); + if (location < 0) { + console.error("No location found with name " + name); + } + + uniform.setUniform(gl, location); + } + + clear(gl: WebGLRenderingContext) { + gl.deleteProgram(this.shader); + } +} + +export interface Uniform { + setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation): void; +} + +export class Uniform2fv implements Uniform { + data: number[] | Float32Array; + constructor(data: number[] | Float32Array) { + this.data = data; + } + + setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) { + gl.uniform2fv(location, this.data); + } +} + +export class Uniform3fv implements Uniform { + data: number[] | Float32Array; + constructor(data: number[] | Float32Array) { + this.data = data; + } + + setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) { + gl.uniform3fv(location, this.data); + } +} + +export class Uniform3f implements Uniform { + x: number; + y: number; + z: number; + + constructor(x: number, y: number, z: number) { + this.x = x; + this.y = y; + this.z = z; + } + + setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) { + gl.uniform3f(location, this.x ,this.y, this.z); + } +} + +export class Uniform1iv implements Uniform { + data: number[] | Int32List; + constructor(data: number[] | Int32List) { + this.data = data; + } + + setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) { + gl.uniform1iv(location, this.data); + } +} + +export class Uniform1i implements Uniform { + texture: number; + + constructor(texture: number) { + this.texture = texture; + } + + setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) { + gl.uniform1i(location, this.texture); + } +} + +export class Uniform1f implements Uniform { + texture: number; + + constructor(texture: number) { + this.texture = texture; + } + + setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) { + gl.uniform1f(location, this.texture); + } +} + +export class Uniform2f implements Uniform { + x: number; + y: number; + + constructor(xy: number[]) { + this.x = xy[0]; + this.y = xy[1]; + } + + setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) { + gl.uniform2f(location, this.x, this.y); + } +} + +export class Uniform4f implements Uniform { + v0: number; + v1: number; + v2: number; + v3: number; + + constructor(xyzw: number[]) { + this.v0 = xyzw[0]; + this.v1 = xyzw[1]; + this.v2 = xyzw[2]; + this.v3 = xyzw[3]; + } + + setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) { + gl.uniform4f(location, this.v0, this.v1, this.v2, this.v3); + } +} + +export class UniformMatrix3fv implements Uniform { + data: number[] | Float32Array; + constructor(data: number[] | Float32Array) { + this.data = data; + } + + setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) { + gl.uniformMatrix3fv(location, false, this.data); + } +} + +export class UniformBool implements Uniform { + data: boolean; + constructor(data: boolean) { + this.data = data; + } + + setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) { + gl.uniform1i(location, this.data ? 1 : 0); + } +} + +export default Shader;
\ No newline at end of file |