UNPKG

@lightningtv/renderer

Version:
1,325 lines (1,236 loc) 31.8 kB
/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ import { assertTruthy } from '../../utils.js'; import { isWebGl2 } from '../renderers/webgl/internal/WebGlUtils.js'; /** * Optimized WebGL Context Wrapper * * @remarks * This class contains the subset of the WebGLRenderingContext & WebGL2RenderingContext * API that is used by the renderer. Select high volume WebGL methods include * caching optimizations to avoid making WebGL calls if the state is already set * to the desired value. * * While most methods contained are direct passthroughs to the WebGL context, * some methods combine multiple WebGL calls into one for convenience, modify * arguments to be more convenient, or are replaced by more specific methods. * * Not all methods are optimized. Only methods that are called frequently * and/or have a high cost are optimized. * * A subset of GLenum constants are also exposed as properties on this class * for convenience. */ export class WebGlContextWrapper { //#region Cached WebGL State private activeTextureUnit = 0; private texture2dUnits: Array<WebGLTexture | null>; private texture2dParams: WeakMap< WebGLTexture, Record<number, number | undefined> > = new WeakMap(); private scissorEnabled; private scissorX: number; private scissorY: number; private scissorWidth: number; private scissorHeight: number; private blendEnabled; private blendSrcRgb: number; private blendDstRgb: number; private blendSrcAlpha: number; private blendDstAlpha: number; private boundArrayBuffer: WebGLBuffer | null; private boundElementArrayBuffer: WebGLBuffer | null; private curProgram: WebGLProgram | null; //#endregion Cached WebGL State //#region Canvas public readonly canvas; //#endregion Canvas //#region WebGL Enums public readonly MAX_RENDERBUFFER_SIZE; public readonly MAX_TEXTURE_SIZE; public readonly MAX_VIEWPORT_DIMS; public readonly MAX_VERTEX_TEXTURE_IMAGE_UNITS; public readonly MAX_TEXTURE_IMAGE_UNITS; public readonly MAX_COMBINED_TEXTURE_IMAGE_UNITS; public readonly MAX_VERTEX_ATTRIBS; public readonly MAX_VARYING_VECTORS; public readonly MAX_VERTEX_UNIFORM_VECTORS; public readonly MAX_FRAGMENT_UNIFORM_VECTORS; public readonly TEXTURE_MAG_FILTER; public readonly TEXTURE_MIN_FILTER; public readonly TEXTURE_WRAP_S; public readonly TEXTURE_WRAP_T; public readonly LINEAR; public readonly CLAMP_TO_EDGE; public readonly RGB; public readonly RGBA; public readonly UNSIGNED_BYTE; public readonly UNPACK_PREMULTIPLY_ALPHA_WEBGL; public readonly UNPACK_FLIP_Y_WEBGL; public readonly FLOAT; public readonly TRIANGLES; public readonly UNSIGNED_SHORT; public readonly ONE; public readonly ONE_MINUS_SRC_ALPHA; public readonly VERTEX_SHADER; public readonly FRAGMENT_SHADER; public readonly STATIC_DRAW; public readonly COMPILE_STATUS; public readonly LINK_STATUS; public readonly DYNAMIC_DRAW; public readonly COLOR_ATTACHMENT0; public readonly INVALID_ENUM: number; public readonly INVALID_OPERATION: number; //#endregion WebGL Enums constructor(private gl: WebGLRenderingContext | WebGL2RenderingContext) { // The following code extracts the current state of the WebGL context // to our local JavaScript cached version of it. This is so we can // avoid making WebGL calls if we don't need to. // We could assume that the WebGL context is in a default state, but // in the future we may want to support restoring a broken WebGL context // and this will help with that. this.activeTextureUnit = (gl.getParameter(gl.ACTIVE_TEXTURE) as number) - gl.TEXTURE0; const maxTextureUnits = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS, ) as number; // save current texture units this.texture2dUnits = new Array<undefined>(maxTextureUnits) .fill(undefined) .map((_, i) => { this.activeTexture(i); return gl.getParameter(gl.TEXTURE_BINDING_2D) as WebGLTexture; }); // restore active texture unit this.activeTexture(this.activeTextureUnit); this.scissorEnabled = gl.isEnabled(gl.SCISSOR_TEST); const scissorBox = gl.getParameter(gl.SCISSOR_BOX) as [ number, number, number, number, ]; this.scissorX = scissorBox[0]; this.scissorY = scissorBox[1]; this.scissorWidth = scissorBox[2]; this.scissorHeight = scissorBox[3]; this.blendEnabled = gl.isEnabled(gl.BLEND); this.blendSrcRgb = gl.getParameter(gl.BLEND_SRC_RGB) as number; this.blendDstRgb = gl.getParameter(gl.BLEND_DST_RGB) as number; this.blendSrcAlpha = gl.getParameter(gl.BLEND_SRC_ALPHA) as number; this.blendDstAlpha = gl.getParameter(gl.BLEND_DST_ALPHA) as number; this.boundArrayBuffer = gl.getParameter( gl.ARRAY_BUFFER_BINDING, ) as WebGLBuffer; this.boundElementArrayBuffer = gl.getParameter( gl.ELEMENT_ARRAY_BUFFER_BINDING, ) as WebGLBuffer; this.curProgram = gl.getParameter( gl.CURRENT_PROGRAM, ) as WebGLProgram | null; this.canvas = gl.canvas; // Extract GLenums this.MAX_RENDERBUFFER_SIZE = gl.MAX_RENDERBUFFER_SIZE; this.MAX_TEXTURE_SIZE = gl.MAX_TEXTURE_SIZE; this.MAX_VIEWPORT_DIMS = gl.MAX_VIEWPORT_DIMS; this.MAX_VERTEX_TEXTURE_IMAGE_UNITS = gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS; this.MAX_TEXTURE_IMAGE_UNITS = gl.MAX_TEXTURE_IMAGE_UNITS; this.MAX_COMBINED_TEXTURE_IMAGE_UNITS = gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS; this.MAX_VERTEX_ATTRIBS = gl.MAX_VERTEX_ATTRIBS; this.MAX_VARYING_VECTORS = gl.MAX_VARYING_VECTORS; this.MAX_VERTEX_UNIFORM_VECTORS = gl.MAX_VERTEX_UNIFORM_VECTORS; this.MAX_FRAGMENT_UNIFORM_VECTORS = gl.MAX_FRAGMENT_UNIFORM_VECTORS; this.TEXTURE_MAG_FILTER = gl.TEXTURE_MAG_FILTER; this.TEXTURE_MIN_FILTER = gl.TEXTURE_MIN_FILTER; this.TEXTURE_WRAP_S = gl.TEXTURE_WRAP_S; this.TEXTURE_WRAP_T = gl.TEXTURE_WRAP_T; this.LINEAR = gl.LINEAR; this.CLAMP_TO_EDGE = gl.CLAMP_TO_EDGE; this.RGB = gl.RGB; this.RGBA = gl.RGBA; this.UNSIGNED_BYTE = gl.UNSIGNED_BYTE; this.UNPACK_PREMULTIPLY_ALPHA_WEBGL = gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL; this.UNPACK_FLIP_Y_WEBGL = gl.UNPACK_FLIP_Y_WEBGL; this.FLOAT = gl.FLOAT; this.TRIANGLES = gl.TRIANGLES; this.UNSIGNED_SHORT = gl.UNSIGNED_SHORT; this.ONE = gl.ONE; this.ONE_MINUS_SRC_ALPHA = gl.ONE_MINUS_SRC_ALPHA; this.MAX_VERTEX_TEXTURE_IMAGE_UNITS = gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS; this.TRIANGLES = gl.TRIANGLES; this.UNSIGNED_SHORT = gl.UNSIGNED_SHORT; this.VERTEX_SHADER = gl.VERTEX_SHADER; this.FRAGMENT_SHADER = gl.FRAGMENT_SHADER; this.STATIC_DRAW = gl.STATIC_DRAW; this.COMPILE_STATUS = gl.COMPILE_STATUS; this.LINK_STATUS = gl.LINK_STATUS; this.DYNAMIC_DRAW = gl.DYNAMIC_DRAW; this.COLOR_ATTACHMENT0 = gl.COLOR_ATTACHMENT0; this.INVALID_ENUM = gl.INVALID_ENUM; this.INVALID_OPERATION = gl.INVALID_OPERATION; } /** * Returns true if the WebGL context is WebGL2 * * @returns */ isWebGl2() { return isWebGl2(this.gl); } /** * ``` * gl.activeTexture(textureUnit + gl.TEXTURE0); * ``` * * @remarks * **WebGL Difference**: `textureUnit` is based from 0, not `gl.TEXTURE0`. * * @param textureUnit */ activeTexture(textureUnit: number) { if (this.activeTextureUnit !== textureUnit) { this.gl.activeTexture(textureUnit + this.gl.TEXTURE0); this.activeTextureUnit = textureUnit; } } /** * ``` * gl.bindTexture(gl.TEXTURE_2D, texture); * ``` * @remarks * **WebGL Difference**: Bind target is always `gl.TEXTURE_2D` * * @param texture */ bindTexture(texture: WebGLTexture | null) { if (this.texture2dUnits[this.activeTextureUnit] === texture) { return; } this.texture2dUnits[this.activeTextureUnit] = texture; this.gl.bindTexture(this.gl.TEXTURE_2D, texture); } private _getActiveTexture(): WebGLTexture | null { return this.texture2dUnits[this.activeTextureUnit]!; } /** * ``` * gl.texParameteri(gl.TEXTURE_2D, pname, param); * ``` * @remarks * **WebGL Difference**: Bind target is always `gl.TEXTURE_2D` * * @param pname * @param param * @returns */ texParameteri(pname: number, param: number) { const activeTexture = this._getActiveTexture(); if (!activeTexture) { throw new Error('No active texture'); } let textureParams = this.texture2dParams.get(activeTexture); if (!textureParams) { textureParams = {}; this.texture2dParams.set(activeTexture, textureParams); } if (textureParams[pname] === param) { return; } textureParams[pname] = param; this.gl.texParameteri(this.gl.TEXTURE_2D, pname, param); } /** * ``` * gl.texImage2D( * gl.TEXTURE_2D, * level, * internalFormat, * width, * height, * border, * format, * type, * pixels, * ); * ``` * @remarks * **WebGL Difference**: Bind target is always `gl.TEXTURE_2D` * * @param level * @param internalFormat * @param width * @param height * @param border * @param format * @param type * @param pixels */ texImage2D( level: GLint, internalformat: GLint, width: GLsizei, height: GLsizei, border: GLint, format: GLenum, type: GLenum, pixels: ArrayBufferView | null, ): void; texImage2D( level: GLint, internalformat: GLint, format: GLenum, type: GLenum, source: TexImageSource | Uint8Array, ): void; texImage2D( level: any, internalFormat: any, widthOrFormat: any, heightOrType: any, borderOrSource: any, format?: any, type?: any, pixels?: any, ) { if (format) { this.gl.texImage2D( this.gl.TEXTURE_2D, level, internalFormat, widthOrFormat, heightOrType, borderOrSource, format, type, pixels, ); } else { this.gl.texImage2D( this.gl.TEXTURE_2D, level, internalFormat, widthOrFormat, heightOrType, borderOrSource, ); } } /** * ``` * gl.compressedTexImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, border, data); * ``` * * @remarks * **WebGL Difference**: Bind target is always `gl.TEXTURE_2D` */ compressedTexImage2D( level: GLint, internalformat: GLenum, width: GLsizei, height: GLsizei, border: GLint, data?: ArrayBufferView, ): void { this.gl.compressedTexImage2D( this.gl.TEXTURE_2D, level, internalformat, width, height, border, data as ArrayBufferView, ); } /** * ``` * gl.pixelStorei(pname, param); * ``` * * @param pname * @param param */ pixelStorei(pname: GLenum, param: GLint | GLboolean) { this.gl.pixelStorei(pname, param); } /** * ``` * gl.generateMipmap(gl.TEXTURE_2D); * ``` * * @remarks * **WebGL Difference**: Bind target is always `gl.TEXTURE_2D` */ generateMipmap() { this.gl.generateMipmap(this.gl.TEXTURE_2D); } /** * ``` * gl.createTexture(); * ``` * * @returns */ createTexture() { return this.gl.createTexture(); } /** * ``` * gl.deleteTexture(texture); * ``` * * @param texture */ deleteTexture(texture: WebGLTexture | null) { if (texture) { this.texture2dParams.delete(texture); } this.gl.deleteTexture(texture); } /** * ``` * gl.deleteFramebuffer(framebuffer); * * @param framebuffer */ deleteFramebuffer(framebuffer: WebGLFramebuffer | null) { this.gl.deleteFramebuffer(framebuffer); } /** * ``` * gl.viewport(x, y, width, height); * ``` */ viewport(x: GLint, y: GLint, width: GLsizei, height: GLsizei) { this.gl.viewport(x, y, width, height); } /** * ``` * gl.clearColor(red, green, blue, alpha); * ``` * * @param red * @param green * @param blue * @param alpha */ clearColor(red: GLclampf, green: GLclampf, blue: GLclampf, alpha: GLclampf) { this.gl.clearColor(red, green, blue, alpha); } /** * ``` * gl["enable"|"disable"](gl.SCISSOR_TEST); * ``` * @param enable */ setScissorTest(enable: boolean) { if (enable === this.scissorEnabled) { return; } if (enable) { this.gl.enable(this.gl.SCISSOR_TEST); } else { this.gl.disable(this.gl.SCISSOR_TEST); } this.scissorEnabled = enable; } /** * ``` * gl.scissor(x, y, width, height); * ``` * * @param x * @param y * @param width * @param height */ scissor(x: GLint, y: GLint, width: GLsizei, height: GLsizei) { if ( x !== this.scissorX || y !== this.scissorY || width !== this.scissorWidth || height !== this.scissorHeight ) { this.gl.scissor(x, y, width, height); this.scissorX = x; this.scissorY = y; this.scissorWidth = width; this.scissorHeight = height; } } /** * ``` * gl["enable"|"disable"](gl.BLEND); * ``` * * @param blend * @returns */ setBlend(blend: boolean) { if (blend === this.blendEnabled) { return; } if (blend) { this.gl.enable(this.gl.BLEND); } else { this.gl.disable(this.gl.BLEND); } this.blendEnabled = blend; } /** * ``` * gl.blendFunc(src, dst); * ``` * * @param src * @param dst */ blendFunc(src: GLenum, dst: GLenum) { if ( src !== this.blendSrcRgb || dst !== this.blendDstRgb || src !== this.blendSrcAlpha || dst !== this.blendDstAlpha ) { this.gl.blendFunc(src, dst); this.blendSrcRgb = src; this.blendDstRgb = dst; this.blendSrcAlpha = src; this.blendDstAlpha = dst; } } /** * ``` * gl.createBuffer(); * ``` * * @returns */ createBuffer() { return this.gl.createBuffer(); } /** * ``` * gl.createFramebuffer(); * ``` * @returns */ createFramebuffer() { return this.gl.createFramebuffer(); } /** * ``` * gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); * ``` * * @param framebuffer */ bindFramebuffer(framebuffer: WebGLFramebuffer | null) { this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, framebuffer); } /** * ``` * gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); * ``` * @remarks * **WebGL Difference**: Bind target is always `gl.FRAMEBUFFER` and textarget is always `gl.TEXTURE_2D` */ framebufferTexture2D( attachment: GLenum, texture: WebGLTexture | null, level: GLint, ) { const gl = this.gl; gl.framebufferTexture2D( gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, texture, level, ); } /** * ``` * gl.clear(gl.COLOR_BUFFER_BIT); * ``` * * @remarks * **WebGL Difference**: Clear mask is always `gl.COLOR_BUFFER_BIT` */ clear() { this.gl.clear(this.gl.COLOR_BUFFER_BIT); } /** * ``` * gl.bindBuffer(gl.ARRAY_BUFFER, buffer); * gl.bufferData(gl.ARRAY_BUFFER, data, usage); * ``` * * @remarks * **WebGL Combo**: `gl.bindBuffer` and `gl.bufferData` are combined into one function. * * @param buffer * @param data * @param usage */ arrayBufferData( buffer: WebGLBuffer | null, data: ArrayBufferView, usage: GLenum, ) { if (this.boundArrayBuffer !== buffer) { this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer); this.boundArrayBuffer = buffer; } this.gl.bufferData(this.gl.ARRAY_BUFFER, data, usage); } /** * ``` * gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer); * gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, usage); * ``` * @remarks * **WebGL Combo**: `gl.bindBuffer` and `gl.bufferData` are combined into one function. * * @param buffer * @param data * @param usage */ elementArrayBufferData( buffer: WebGLBuffer | null, data: ArrayBufferView, usage: GLenum, ) { if (this.boundElementArrayBuffer !== buffer) { this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, buffer); this.boundElementArrayBuffer = buffer; } this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, data, usage); } /** * ``` * gl.bindBuffer(gl.ARRAY_BUFFER, buffer); * gl.vertexAttribPointer(index, size, type, normalized, stride, offset); * ``` * * @remarks * **WebGL Combo**: `gl.bindBuffer` and `gl.vertexAttribPointer` are combined into one function. * * @param buffer * @param index * @param size * @param type * @param normalized * @param stride * @param offset */ vertexAttribPointer( buffer: WebGLBuffer, index: GLuint, size: GLint, type: GLenum, normalized: GLboolean, stride: GLsizei, offset: GLintptr, ) { if (this.boundArrayBuffer !== buffer) { this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer); this.boundArrayBuffer = buffer; } this.gl.vertexAttribPointer(index, size, type, normalized, stride, offset); } /** * Returns object with Attribute names as key and numbers as location values * * @param program * @returns object with numbers */ getUniformLocations(program: WebGLProgram): Record<string, number> { const gl = this.gl; const length = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS, ) as number; const result = {} as Record<string, number>; for (let i = 0; i < length; i++) { const { name } = gl.getActiveUniform(program, i) as WebGLActiveInfo; result[name] = i; } return result; } /** * Returns object with Attribute names as key and numbers as location values * @param program * @returns object with numbers */ getAttributeLocations(program: WebGLProgram): Record<string, number> { const gl = this.gl; const length = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES, ) as number; const result = {} as Record<string, number>; for (let i = 0; i < length; i++) { const { name } = gl.getActiveAttrib(program, i) as WebGLActiveInfo; result[name] = i; } return result; } /** * ``` * gl.useProgram(program); * ``` * * @param program * @returns */ useProgram(program: WebGLProgram | null) { if (this.curProgram === program) { return; } this.gl.useProgram(program); this.curProgram = program; } /** * Sets the value of a single float uniform variable. * * @param location - The location of the uniform variable. * @param v0 - The value to set. */ uniform1f(location: string, v0: number) { this.gl.uniform1f( this.gl.getUniformLocation(this.curProgram!, location), v0, ); } /** * Sets the value of a float array uniform variable. * * @param location - The location of the uniform variable. * @param value - The array of values to set. */ uniform1fv(location: string, value: Float32Array) { this.gl.uniform1fv( this.gl.getUniformLocation(this.curProgram!, location), value, ); } /** * Sets the value of a single integer uniform variable. * * @param location - The location of the uniform variable. * @param v0 - The value to set. */ uniform1i(location: string, v0: number) { this.gl.uniform1i( this.gl.getUniformLocation(this.curProgram!, location), v0, ); } /** * Sets the value of an integer array uniform variable. * * @param location - The location of the uniform variable. * @param value - The array of values to set. */ uniform1iv(location: string, value: Int32Array) { this.gl.uniform1iv( this.gl.getUniformLocation(this.curProgram!, location), value, ); } /** * Sets the value of a vec2 uniform variable. * * @param location - The location of the uniform variable. * @param v0 - The first component of the vector. * @param v1 - The second component of the vector. */ uniform2f(location: string, v0: number, v1: number) { this.gl.uniform2f( this.gl.getUniformLocation(this.curProgram!, location), v0, v1, ); } /** * Sets the value of a vec2 array uniform variable. * * @param location - The location of the uniform variable. * @param value - The array of vec2 values to set. */ uniform2fv(location: string, value: Float32Array) { this.gl.uniform2fv( this.gl.getUniformLocation(this.curProgram!, location), value, ); } /** * Sets the value of a ivec2 uniform variable. * * @param location - The location of the uniform variable. * @param v0 - The first component of the vector. * @param v1 - The second component of the vector. */ uniform2i(location: string, v0: number, v1: number) { this.gl.uniform2i( this.gl.getUniformLocation(this.curProgram!, location), v0, v1, ); } /** * Sets the value of an ivec2 array uniform variable. * * @param location - The location of the uniform variable. * @param value - The array of ivec2 values to set. */ uniform2iv(location: string, value: Int32Array) { this.gl.uniform2iv( this.gl.getUniformLocation(this.curProgram!, location), value, ); } /** * Sets the value of a vec3 uniform variable. * * @param location - The location of the uniform variable. * @param v0 - The first component of the vector. * @param v1 - The second component of the vector. * @param v2 - The third component of the vector. */ uniform3f(location: string, v0: number, v1: number, v2: number) { this.gl.uniform3f( this.gl.getUniformLocation(this.curProgram!, location), v0, v1, v2, ); } /** * Sets the value of a vec3 array uniform variable. * * @param location - The location of the uniform variable. * @param value - The array of vec3 values to set. */ uniform3fv(location: string, value: Float32Array) { this.gl.uniform3fv( this.gl.getUniformLocation(this.curProgram!, location), value, ); } /** * Sets the value of a ivec3 uniform variable. * * @param location - The location of the uniform variable. * @param v0 - The first component of the vector. * @param v1 - The second component of the vector. * @param v2 - The third component of the vector. */ uniform3i(location: string, v0: number, v1: number, v2: number) { this.gl.uniform3i( this.gl.getUniformLocation(this.curProgram!, location), v0, v1, v2, ); } /** * Sets the value of an ivec3 array uniform variable. * * @param location - The location of the uniform variable. * @param value - The array of ivec3 values to set. */ uniform3iv(location: string, value: Int32Array) { this.gl.uniform3iv( this.gl.getUniformLocation(this.curProgram!, location), value, ); } /** * Sets the value of a vec4 uniform variable. * * @param location - The location of the uniform variable. * @param v0 - The first component of the vector. * @param v1 - The second component of the vector. * @param v2 - The third component of the vector. * @param v3 - The fourth component of the vector. */ uniform4f(location: string, v0: number, v1: number, v2: number, v3: number) { this.gl.uniform4f( this.gl.getUniformLocation(this.curProgram!, location), v0, v1, v2, v3, ); } /** * Sets the value of a vec4 array uniform variable. * * @param location - The location of the uniform variable. * @param value - The array of vec4 values to set. */ uniform4fv(location: string, value: Float32Array) { this.gl.uniform4fv( this.gl.getUniformLocation(this.curProgram!, location), value, ); } /** * Sets the value of a ivec4 uniform variable. * * @param location - The location of the uniform variable. * @param v0 - The first component of the vector. * @param v1 - The second component of the vector. * @param v2 - The third component of the vector. * @param v3 - The fourth component of the vector. */ uniform4i(location: string, v0: number, v1: number, v2: number, v3: number) { this.gl.uniform4i( this.gl.getUniformLocation(this.curProgram!, location), v0, v1, v2, v3, ); } /** * Sets the value of an ivec4 array uniform variable. * * @param location - The location of the uniform variable. * @param value - The array of ivec4 values to set. */ uniform4iv(location: string, value: Int32Array) { this.gl.uniform4iv( this.gl.getUniformLocation(this.curProgram!, location), value, ); } /** * Sets the value of a mat2 uniform variable. * * @param location - The location of the uniform variable. * @param transpose - Whether to transpose the matrix. * @param value - The array of mat2 values to set. */ uniformMatrix2fv(location: string, value: Float32Array) { this.gl.uniformMatrix2fv( this.gl.getUniformLocation(this.curProgram!, location), false, value, ); } /** * Sets the value of a mat2 uniform variable. * @param location - The location of the uniform variable. * @param value - The array of mat2 values to set. */ uniformMatrix3fv(location: string, value: Float32Array) { this.gl.uniformMatrix3fv( this.gl.getUniformLocation(this.curProgram!, location), false, value, ); } /** * Sets the value of a mat4 uniform variable. * @param location - The location of the uniform variable. * @param value - The array of mat4 values to set. */ uniformMatrix4fv(location: string, value: Float32Array) { this.gl.uniformMatrix4fv( this.gl.getUniformLocation(this.curProgram!, location), false, value, ); } /** * ``` * gl.getParameter(pname); * ``` * * @param pname * @returns */ getParameter(pname: GLenum): any { return this.gl.getParameter(pname); } /** * ``` * gl.drawElements(mode, count, type, offset); * ``` * * @param mode * @param count * @param type * @param offset */ drawElements(mode: GLenum, count: GLsizei, type: GLenum, offset: GLintptr) { this.gl.drawElements(mode, count, type, offset); } /** * ``` * gl.drawArrays(mode, first, count); * ``` * * @param name * @returns */ getExtension(name: string) { return this.gl.getExtension(name); } /** * ``` * gl.getError(type); * ``` * * @returns */ getError() { return this.gl.getError(); } /** * ``` * gl.createVertexArray(); * ``` * * @returns */ createVertexArray() { if (this.gl instanceof WebGL2RenderingContext) { return this.gl.createVertexArray(); } return undefined; } /** * ``` * gl.bindVertexArray(vertexArray); * ``` * * @param vertexArray */ bindVertexArray(vertexArray: WebGLVertexArrayObject | null) { if (this.gl instanceof WebGL2RenderingContext) { this.gl.bindVertexArray(vertexArray); } } /** * ``` * gl.getAttribLocation(program, name); * ``` * * @param program * @param name * @returns */ getAttribLocation(program: WebGLProgram, name: string) { return this.gl.getAttribLocation(program, name); } /** * ``` * gl.getUniformLocation(program, name); * ``` * * @param program * @param name * @returns */ getUniformLocation(program: WebGLProgram, name: string) { return this.gl.getUniformLocation(program, name); } /** * ``` * gl.enableVertexAttribArray(index); * ``` * * @param index */ enableVertexAttribArray(index: number) { this.gl.enableVertexAttribArray(index); } /** * ``` * gl.disableVertexAttribArray(index); * ``` * * @param index */ disableVertexAttribArray(index: number) { this.gl.disableVertexAttribArray(index); } /** * ``` * gl.createShader(type); * ``` * * @param type * @returns */ createShader(type: number) { return this.gl.createShader(type); } /** * ``` * gl.compileShader(shader); * ``` * * @param shader * @returns */ compileShader(shader: WebGLShader) { this.gl.compileShader(shader); } /** * ``` * gl.attachShader(program, shader); * ``` * * @param program * @param shader */ attachShader(program: WebGLProgram, shader: WebGLShader) { this.gl.attachShader(program, shader); } /** * ``` * gl.linkProgram(program); * ``` * * @param program */ linkProgram(program: WebGLProgram) { this.gl.linkProgram(program); } /** * ``` * gl.deleteProgram(shader); * ``` * * @param shader */ deleteProgram(shader: WebGLProgram) { this.gl.deleteProgram(shader); } /** * ``` * gl.getShaderParameter(shader, pname); * ``` * * @param shader * @param pname */ getShaderParameter(shader: WebGLShader, pname: GLenum) { return this.gl.getShaderParameter(shader, pname); } /** * ``` * gl.getShaderInfoLog(shader); * ``` * * @param shader */ getShaderInfoLog(shader: WebGLShader) { return this.gl.getShaderInfoLog(shader); } /** * ``` * gl.createProgram(); * ``` * * @returns */ createProgram() { return this.gl.createProgram(); } /** * ``` * gl.getProgramParameter(program, pname); * ``` * * @param program * @param pname * @returns */ getProgramParameter(program: WebGLProgram, pname: GLenum) { return this.gl.getProgramParameter(program, pname); } /** * ``` * gl.getProgramInfoLog(program); * ``` * * @param program * @returns */ getProgramInfoLog(program: WebGLProgram) { return this.gl.getProgramInfoLog(program); } /** * ``` * gl.shaderSource(shader, source); * ``` * * @param shader * @param source */ shaderSource(shader: WebGLShader, source: string) { this.gl.shaderSource(shader, source); } /** * ``` * gl.deleteShader(shader); * ``` * * @param shader */ deleteShader(shader: WebGLShader) { this.gl.deleteShader(shader); } } // prettier-ignore type IsUniformMethod<MethodName, MethodType> = MethodName extends `uniform${string}` ? MethodType extends (location: WebGLUniformLocation | null, ...args: any[]) => void ? true : false : false; // prettier-ignore export type UniformMethodMap = { [Key in keyof WebGLRenderingContext as IsUniformMethod<Key, WebGLRenderingContext[Key]> extends true ? Key : never]: WebGLRenderingContext[Key] extends ( location: WebGLUniformLocation | null, ...args: infer T ) => void ? T : never; }; /** * Compare two arrays for equality. * * @remarks * This function will not try to compare nested arrays or Float32Arrays and * instead will always return false when they are encountered. * * @param a * @param b * @returns */ export function compareArrays<T>(a: T[], b: T[]): boolean { if (a.length !== b.length) { return false; } let result = false; for (let i = 0; i < a.length; i++) { if (Array.isArray(a[i]) || a[i] instanceof Float32Array) { result = false; break; } if (a[i] !== b[i]) { result = false; break; } result = true; } return result; }