From 711c4c22643522e3713916a89de470c256d01d91 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Mon, 18 Nov 2024 22:16:29 +0800 Subject: [PATCH] refactor: gl2 & shader --- src/core/render/gl2.ts | 1753 ++++++++++++++++++++++++++ src/core/render/shader.ts | 2265 +++++++--------------------------- src/module/weather/rain.ts | 42 +- src/module/weather/snow.ts | 44 +- src/plugin/boss/towerBoss.ts | 4 +- src/plugin/fx/pointShader.ts | 6 +- src/plugin/game/fallback.ts | 47 +- 7 files changed, 2237 insertions(+), 1924 deletions(-) create mode 100644 src/core/render/gl2.ts diff --git a/src/core/render/gl2.ts b/src/core/render/gl2.ts new file mode 100644 index 0000000..380c26b --- /dev/null +++ b/src/core/render/gl2.ts @@ -0,0 +1,1753 @@ +import EventEmitter from 'eventemitter3'; +import { logger } from '../common/logger'; +import { MotaOffscreenCanvas2D } from '../fx/canvas2d'; +import { RenderItem, RenderItemPosition } from './item'; +import { Transform } from './transform'; +import { isWebGL2Supported } from '../fx/webgl'; + +export interface IGL2ProgramPrefix { + readonly VERTEX: string; + readonly FRAGMENT: string; +} + +const GL2_PREFIX: IGL2ProgramPrefix = { + VERTEX: /* glsl */ `#version 300 es +precision highp float; +`, + FRAGMENT: /* glsl */ `#version 300 es +precision highp float; +` +}; + +interface CompiledShader { + vertex: WebGLShader; + fragment: WebGLShader; +} + +const enum RenderMode { + Arrays, + Elements, + ArraysInstanced, + ElementsInstanced +} + +export const enum UniformType { + Uniform1f, + Uniform1fv, + Uniform1i, + Uniform1iv, + Uniform1ui, + Uniform1uiv, + Uniform2f, + Uniform2fv, + Uniform2i, + Uniform2iv, + Uniform2ui, + Uniform2uiv, + Uniform3f, + Uniform3fv, + Uniform3i, + Uniform3iv, + Uniform3ui, + Uniform3uiv, + Uniform4f, + Uniform4fv, + Uniform4i, + Uniform4iv, + Uniform4ui, + Uniform4uiv +} + +export const enum UniformMatrix { + UMatrix2x2, + UMatrix2x3, + UMatrix2x4, + UMatrix3x2, + UMatrix3x3, + UMatrix3x4, + UMatrix4x2, + UMatrix4x3, + UMatrix4x4 +} + +export const enum AttribType { + Attrib1f, + Attrib1fv, + Attrib2f, + Attrib2fv, + Attrib3f, + Attrib3fv, + Attrib4f, + Attrib4fv, + AttribI4i, + AttribI4iv, + AttribI4ui, + AttribI4uiv +} + +export type ProgramConstructor = new ( + gl2: GL2, + vs?: string, + fs?: string +) => T; + +export abstract class GL2 extends RenderItem { + /** 是否支持此组件 */ + static readonly support: boolean = isWebGL2Supported(); + + // 会用到的一些常量 + // uniform 类型 + readonly UNIFORM_1f: UniformType.Uniform1f = UniformType.Uniform1f; + readonly UNIFORM_1fv: UniformType.Uniform1fv = UniformType.Uniform1fv; + readonly UNIFORM_1i: UniformType.Uniform1i = UniformType.Uniform1i; + readonly UNIFORM_1iv: UniformType.Uniform1iv = UniformType.Uniform1iv; + readonly UNIFORM_1ui: UniformType.Uniform1ui = UniformType.Uniform1ui; + readonly UNIFORM_1uiv: UniformType.Uniform1uiv = UniformType.Uniform1uiv; + readonly UNIFORM_2f: UniformType.Uniform2f = UniformType.Uniform2f; + readonly UNIFORM_2fv: UniformType.Uniform2fv = UniformType.Uniform2fv; + readonly UNIFORM_2i: UniformType.Uniform2i = UniformType.Uniform2i; + readonly UNIFORM_2iv: UniformType.Uniform2iv = UniformType.Uniform2iv; + readonly UNIFORM_2ui: UniformType.Uniform2ui = UniformType.Uniform2ui; + readonly UNIFORM_2uiv: UniformType.Uniform2uiv = UniformType.Uniform2uiv; + readonly UNIFORM_3f: UniformType.Uniform3f = UniformType.Uniform3f; + readonly UNIFORM_3fv: UniformType.Uniform3fv = UniformType.Uniform3fv; + readonly UNIFORM_3i: UniformType.Uniform3i = UniformType.Uniform3i; + readonly UNIFORM_3iv: UniformType.Uniform3iv = UniformType.Uniform3iv; + readonly UNIFORM_3ui: UniformType.Uniform3ui = UniformType.Uniform3ui; + readonly UNIFORM_3uiv: UniformType.Uniform3uiv = UniformType.Uniform3uiv; + readonly UNIFORM_4f: UniformType.Uniform4f = UniformType.Uniform4f; + readonly UNIFORM_4fv: UniformType.Uniform4fv = UniformType.Uniform4fv; + readonly UNIFORM_4i: UniformType.Uniform4i = UniformType.Uniform4i; + readonly UNIFORM_4iv: UniformType.Uniform4iv = UniformType.Uniform4iv; + readonly UNIFORM_4ui: UniformType.Uniform4ui = UniformType.Uniform4ui; + readonly UNIFORM_4uiv: UniformType.Uniform4uiv = UniformType.Uniform4uiv; + // uniform matrix 类型 + readonly U_MATRIX_2x2: UniformMatrix.UMatrix2x2 = UniformMatrix.UMatrix2x2; + readonly U_MATRIX_2x3: UniformMatrix.UMatrix2x3 = UniformMatrix.UMatrix2x3; + readonly U_MATRIX_2x4: UniformMatrix.UMatrix2x4 = UniformMatrix.UMatrix2x4; + readonly U_MATRIX_3x2: UniformMatrix.UMatrix3x2 = UniformMatrix.UMatrix3x2; + readonly U_MATRIX_3x3: UniformMatrix.UMatrix3x3 = UniformMatrix.UMatrix3x3; + readonly U_MATRIX_3x4: UniformMatrix.UMatrix3x4 = UniformMatrix.UMatrix3x4; + readonly U_MATRIX_4x2: UniformMatrix.UMatrix4x2 = UniformMatrix.UMatrix4x2; + readonly U_MATRIX_4x3: UniformMatrix.UMatrix4x3 = UniformMatrix.UMatrix4x3; + readonly U_MATRIX_4x4: UniformMatrix.UMatrix4x4 = UniformMatrix.UMatrix4x4; + // attribute 类型 + readonly ATTRIB_1f: AttribType.Attrib1f = AttribType.Attrib1f; + readonly ATTRIB_1fv: AttribType.Attrib1fv = AttribType.Attrib1fv; + readonly ATTRIB_2f: AttribType.Attrib2f = AttribType.Attrib2f; + readonly ATTRIB_2fv: AttribType.Attrib2fv = AttribType.Attrib2fv; + readonly ATTRIB_3f: AttribType.Attrib3f = AttribType.Attrib3f; + readonly ATTRIB_3fv: AttribType.Attrib3fv = AttribType.Attrib3fv; + readonly ATTRIB_4f: AttribType.Attrib4f = AttribType.Attrib4f; + readonly ATTRIB_4fv: AttribType.Attrib4fv = AttribType.Attrib4fv; + readonly ATTRIB_I4i: AttribType.AttribI4i = AttribType.AttribI4i; + readonly ATTRIB_I4iv: AttribType.AttribI4iv = AttribType.AttribI4iv; + readonly ATTRIB_I4ui: AttribType.AttribI4ui = AttribType.AttribI4ui; + readonly ATTRIB_I4uiv: AttribType.AttribI4uiv = AttribType.AttribI4uiv; + // 渲染模式 + readonly DRAW_ARRAYS = RenderMode.Arrays; + readonly DRAW_ELEMENTS = RenderMode.Elements; + readonly DRAW_ARRAYS_INSTANCED = RenderMode.ArraysInstanced; + readonly DRAW_ELEMENTS_INSTANCED = RenderMode.ElementsInstanced; + // 其他常量 + readonly MAX_TEXTURE_COUNT: number = 0; + + canvas: HTMLCanvasElement; + gl: WebGL2RenderingContext; + + /** webgl使用的程序 */ + protected program: GL2Program | null = null; + /** 当前渲染实例的所有着色器程序 */ + protected programs: Set = new Set(); + /** framebuffer 映射 */ + protected framebufferMap: Map = new Map(); + + constructor(type: RenderItemPosition = 'static') { + super(type, !GL2.support); + + this.canvas = document.createElement('canvas'); + this.gl = this.canvas.getContext('webgl2')!; + if (!GL2.support) { + this.canvas.width = 0; + this.canvas.height = 0; + } else { + const num = this.gl.getParameter(this.gl.MAX_TEXTURE_IMAGE_UNITS); + if (typeof num === 'number') { + this.MAX_TEXTURE_COUNT = num; + } + } + + this.init(); + } + + protected render( + canvas: MotaOffscreenCanvas2D, + transform: Transform + ): void { + if (!GL2.support || !this.program) return; + const compile = this.program.requestCompile(); + if (compile) { + this.gl.useProgram(this.program.program); + } + + if (this.cacheDirty) { + this.drawScene(canvas, transform); + this.cacheDirty = false; + } + + canvas.ctx.drawImage(this.canvas, 0, 0, this.width, this.height); + } + + drawScene(canvas: MotaOffscreenCanvas2D, transform: Transform) { + const gl = this.gl; + const program = this.program; + if (!gl || !program) return; + const ready = program.ready(); + if (!ready) return; + + // 清空画布 + gl.viewport(0, 0, this.canvas.width, this.canvas.height); + gl.clearColor(0, 0, 0, 0); + gl.clearDepth(1); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + const pre = this.preDraw(canvas, transform, gl, program); + if (!pre) { + this.postDraw(canvas, transform, gl, program); + return; + } + + this.draw(gl, program); + + this.postDraw(canvas, transform, gl, program); + } + + protected abstract preDraw( + canvas: MotaOffscreenCanvas2D, + transform: Transform, + gl: WebGL2RenderingContext, + program: GL2Program + ): boolean; + + protected abstract postDraw( + canvas: MotaOffscreenCanvas2D, + transform: Transform, + gl: WebGL2RenderingContext, + program: GL2Program + ): void; + + draw(gl: WebGL2RenderingContext, program: GL2Program) { + const indices = program.usingIndices; + const param = program.getDrawParams(program.renderMode); + if (!param) return; + switch (program.renderMode) { + case RenderMode.Arrays: { + const { mode, first, count } = param as DrawArraysParam; + gl.drawArrays(mode, first, count); + } + case RenderMode.Elements: { + if (!indices) return; + const { mode, count, type, offset } = + param as DrawElementsParam; + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices.data); + gl.drawElements(mode, count, type, offset); + } + case RenderMode.ArraysInstanced: { + const { mode, first, count, instanceCount } = + param as DrawArraysInstancedParam; + gl.drawArraysInstanced(mode, first, count, instanceCount); + } + case RenderMode.ElementsInstanced: { + if (!indices) return; + const { + mode, + count, + type, + offset, + instanceCount: ins + } = param as DrawElementsInstancedParam; + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices.data); + gl.drawElementsInstanced(mode, count, type, offset, ins); + } + } + } + + /** + * 将画面渲染至帧缓冲 + * @param name 帧缓冲名称 + * @param texture 渲染至的纹理 + * @param clear 是否先清空画布再渲染 + */ + framebuffer( + name: string, + texture: IShaderTexture2D, + clear: boolean = true + ) { + const gl = this.gl; + const buffer = this.framebufferMap.get(name); + const program = this.program; + if (!gl || !buffer || !program) return; + + const tex = texture.texture; + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer); + if (clear) { + gl.viewport(0, 0, this.canvas.width, this.canvas.height); + gl.clearColor(0, 0, 0, 0); + gl.clearDepth(1); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + tex, + 0 + ); + this.draw(gl, program); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + /** + * 创建一个帧缓冲对象 + * @param name 帧缓冲名称 + * @returns 是否创建成功 + */ + createFramebuffer(name: string): boolean { + const gl = this.gl; + if (!gl) return false; + const buffer = gl.createFramebuffer(); + if (!buffer) return false; + this.framebufferMap.set(name, buffer); + return true; + } + + /** + * 删除一个帧缓冲对象 + * @param name 帧缓冲名称 + * @returns 是否删除成功 + */ + deleteFramebuffer(name: string): boolean { + const gl = this.gl; + if (!gl) return false; + const buffer = this.framebufferMap.get(name); + if (!buffer) return false; + gl.deleteFramebuffer(buffer); + return this.framebufferMap.delete(name); + } + + /** + * 切换着色器程序 + * @param program 着色器程序 + */ + useProgram(program: GL2Program) { + if (!this.gl) return; + if (program.element !== this) { + logger.error(17); + return; + } + if (this.program !== program) { + this.program?.unload(); + this.program = program; + this.gl.useProgram(program.program); + program.load(); + } + } + + /** + * 创建一个着色器程序 + * @param vs 顶点着色器,可选 + * @param fs 片元着色器,可选 + */ + createProgram( + Program: ProgramConstructor, + vs?: string, + fs?: string + ) { + const program = new Program(this, vs, fs); + this.programs.add(program); + return program; + } + + /** + * 删除一个着色器程序 + * @param program 要删除的着色器程序 + */ + deleteProgram(program: GL2Program) { + if (program.element !== this) { + logger.error(18); + return; + } + program.destroy(); + this.programs.delete(program); + } + + destroy(): void { + this.programs.forEach(v => v.destroy()); + super.destroy(); + } + + private init() { + const gl = this.gl; + if (!gl) return; + gl.enable(gl.DEPTH_TEST); + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + gl.depthFunc(gl.LEQUAL); + } +} + +type _U1 = [x0: number]; +type _U2 = [x0: number, x1: number]; +type _U3 = [x0: number, x1: number, x2: number]; +type _U4 = [x0: number, x1: number, x2: number, x3: number]; +type _UV = [data: T, srcOffset?: number, srcLength?: number]; +type _A = [data: T]; + +interface UniformSetFn { + [UniformType.Uniform1f]: _U1; + [UniformType.Uniform1fv]: _UV; + [UniformType.Uniform1i]: _U1; + [UniformType.Uniform1iv]: _UV; + [UniformType.Uniform1ui]: _U1; + [UniformType.Uniform1uiv]: _UV; + [UniformType.Uniform2f]: _U2; + [UniformType.Uniform2fv]: _UV; + [UniformType.Uniform2i]: _U2; + [UniformType.Uniform2iv]: _UV; + [UniformType.Uniform2ui]: _U2; + [UniformType.Uniform2uiv]: _UV; + [UniformType.Uniform3f]: _U3; + [UniformType.Uniform3fv]: _UV; + [UniformType.Uniform3i]: _U3; + [UniformType.Uniform3iv]: _UV; + [UniformType.Uniform3ui]: _U3; + [UniformType.Uniform3uiv]: _UV; + [UniformType.Uniform4f]: _U4; + [UniformType.Uniform4fv]: _UV; + [UniformType.Uniform4i]: _U4; + [UniformType.Uniform4iv]: _UV; + [UniformType.Uniform4ui]: _U4; + [UniformType.Uniform4uiv]: _UV; +} + +interface AttribSetFn { + [AttribType.Attrib1f]: _U1; + [AttribType.Attrib1fv]: _A; + [AttribType.Attrib2f]: _U2; + [AttribType.Attrib2fv]: _A; + [AttribType.Attrib3f]: _U3; + [AttribType.Attrib3fv]: _A; + [AttribType.Attrib4f]: _U4; + [AttribType.Attrib4fv]: _A; + [AttribType.AttribI4i]: _U4; + [AttribType.AttribI4iv]: _A; + [AttribType.AttribI4ui]: _U4; + [AttribType.AttribI4uiv]: _A; +} + +export interface IShaderUniform { + /** 这个 uniform 变量的内存位置 */ + readonly location: WebGLUniformLocation; + /** 这个 uniform 变量的类型 */ + readonly type: T; + /** 这个量所处的着色器程序 */ + readonly program: GL2Program; + /** + * 设置这个 uniform 变量的值, + * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL2RenderingContext/uniform + * @param params 要传递的参数,例如 uniform2f 就要传递 x0 x1 两个参数等,可以参考 mdn 文档 + */ + set(...params: UniformSetFn[T]): void; +} + +export interface IShaderAttrib { + /** 这个 attribute 常量的内存位置 */ + readonly location: number; + /** 这个 attribute 常量的类型 */ + readonly type: T; + /** 这个量所处的着色器程序 */ + readonly program: GL2Program; + /** + * 设置这个 attribute 常量的值, + * 浮点数参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/vertexAttrib + * 整数参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/vertexAttribI + * @param params 要传递的参数 + */ + set(...params: AttribSetFn[T]): void; +} + +export interface IShaderAttribArray { + /** 这个 attribute 常量的内存位置 */ + readonly location: number; + /** 这个 attribute 所用的缓冲区信息 */ + readonly data: WebGLBuffer; + /** 这个量所处的着色器程序 */ + readonly program: GL2Program; + /** + * 修改缓冲区数据,会更改数据大小,重新分配内存,不更改数据大小的情况下建议使用 {@link sub} 代替。 + * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/bufferData + * @param data 数据 + * @param usage 用途 + */ + buffer(data: AllowSharedBufferSource | null, usage: GLenum): void; + /** + * 修改缓冲区数据,会更改数据大小,重新分配内存,不更改数据大小的情况下建议使用 {@link sub} 代替。 + * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/bufferData + * @param data 数据 + * @param usage 用途 + * @param srcOffset 数据偏移量 + * @param length 数据长度 + */ + buffer( + data: ArrayBufferView, + usage: GLenum, + srcOffset: number, + length?: number + ): void; + /** + * 修改缓冲区数据,但是不修改数据大小,不重新分配内存。 + * 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferSubData + * @param dstByteOffset 数据修改的起始位置 + * @param srcData 数据 + */ + sub(dstByteOffset: GLintptr, srcData: AllowSharedBufferSource): void; + /** + * 修改缓冲区数据,但是不修改数据大小,不重新分配内存。 + * 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferSubData + * @param dstByteOffset 数据修改的起始位置 + * @param srcData 数据 + * @param srcOffset 数据偏移量 + * @param length 数据长度 + */ + sub( + dstByteOffset: GLintptr, + srcData: ArrayBufferView, + srcOffset: number, + length?: GLuint + ): void; + /** + * 告诉 gpu 将读取此 attribute 数据 + * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/vertexAttribPointer + * @param size 单个数据大小 + * @param type 数据类型 + * @param normalized 是否要经过归一化处理 + * @param stride 每一部分字节偏移量 + * @param offset 第一部分字节偏移量 + */ + pointer( + size: GLint, + type: GLenum, + normalized: GLboolean, + stride: GLsizei, + offset: GLintptr + ): void; + /** + * 告诉 gpu 将由整数类型读取此 attribute 数据 + * 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/vertexAttribIPointer + * @param size 单个数据大小 + * @param type 数据类型 + * @param stride 每一部分字节偏移量 + * @param offset 第一部分字节偏移量 + */ + pointerI( + size: GLint, + type: GLenum, + stride: GLsizei, + offset: GLintptr + ): void; + /** + * 设置顶点指针更新时刻。 + * 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/vertexAttribDivisor + * @param divisor 每多少个实例更新一次,0表示每个顶点都更新 + */ + divisor(divisor: number): void; + /** + * 启用这个顶点数据 + */ + enable(): void; + /** + * 禁用这个顶点数据 + */ + disable(): void; +} + +export interface IShaderIndices { + /** 这个顶点索引所用的缓冲区信息 */ + readonly data: WebGLBuffer; + /** 这个量所处的着色器程序 */ + readonly program: GL2Program; + /** + * 修改缓冲区数据,会更改数据大小,重新分配内存,不更改数据大小的情况下建议使用 {@link sub} 代替。 + * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/bufferData + * @param data 数据 + * @param usage 用途 + */ + buffer(data: AllowSharedBufferSource | null, usage: GLenum): void; + /** + * 修改缓冲区数据,会更改数据大小,重新分配内存,不更改数据大小的情况下建议使用 {@link sub} 代替。 + * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/bufferData + * @param data 数据 + * @param usage 用途 + * @param srcOffset 数据偏移量 + * @param length 数据长度 + */ + buffer( + data: ArrayBufferView, + usage: GLenum, + srcOffset: number, + length?: number + ): void; + /** + * 修改缓冲区数据,但是不修改数据大小,不重新分配内存。 + * 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferSubData + * @param dstByteOffset 数据修改的起始位置 + * @param srcData 数据 + */ + sub(dstByteOffset: GLintptr, srcData: AllowSharedBufferSource): void; + /** + * 修改缓冲区数据,但是不修改数据大小,不重新分配内存。 + * 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferSubData + * @param dstByteOffset 数据修改的起始位置 + * @param srcData 数据 + * @param srcOffset 数据偏移量 + * @param length 数据长度 + */ + sub( + dstByteOffset: GLintptr, + srcData: ArrayBufferView, + srcOffset: number, + length?: GLuint + ): void; +} + +export interface IShaderUniformMatrix { + /** 矩阵的内存位置 */ + readonly location: WebGLUniformLocation; + /** 矩阵类型 */ + readonly type: UniformMatrix; + /** 这个量所处的着色器程序 */ + readonly program: GL2Program; + /** + * 设置矩阵的值,参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL2RenderingContext/uniformMatrix + * @param transpose 是否转置矩阵 + * @param data 矩阵数据,列主序 + * @param srcOffset 数据偏移量 + * @param srcLength 数据长度 + */ + set( + transpose: GLboolean, + data: Float32List, + srcOffset?: number, + srcLength?: number + ): void; +} + +export interface IShaderUniformBlock { + /** 这个 uniform block 的内存地址 */ + readonly location: GLuint; + /** 与这个 uniform block 所绑定的缓冲区 */ + readonly buffer: WebGLBuffer; + /** 这个 uniform block 的大小 */ + readonly size: number; + /** 这个量所处的着色器程序 */ + readonly program: GL2Program; + /** + * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL2RenderingContext/bindBufferBase + * @param srcData 要设置为的值 + */ + set(srcData: AllowSharedBufferSource | null): void; + /** + * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL2RenderingContext/bindBufferBase + * @param srcData 要设置为的值 + * @param srcOffset 数据偏移量 + * @param length 数据长度 + */ + set(srcData: ArrayBufferView, srcOffset: number, length?: number): void; +} + +export interface IShaderTexture2D { + /** 纹理对象 */ + readonly texture: WebGLTexture; + /** 宽度 */ + readonly width: number; + /** 高度 */ + readonly height: number; + /** 纹理所属索引 */ + readonly index: number; + /** 这个量所处的着色器程序 */ + readonly program: GL2Program; + /** + * 设置这个纹理的图像,不建议使用,会修改宽高 + * @param source 要设置成的图像源 + */ + set(source: TexImageSource): void; + /** + * 设置纹理的一部分信息,不会修改宽高 + * @param source 要设置的图像源 + * @param x 要设置到的起始点横坐标 + * @param y 要设置到的起始点纵坐标 + * @param width 宽度 + * @param height 高度 + */ + sub( + source: TexImageSource, + x: number, + y: number, + width: number, + height: number + ): void; +} + +class ShaderUniform implements IShaderUniform { + constructor( + readonly type: T, + readonly location: WebGLUniformLocation, + readonly gl: WebGL2RenderingContext, + readonly program: GL2Program + ) {} + + set(...params: UniformSetFn[T]): void { + this.gl.vertexAttribIPointer; + // 因为ts类型推导的限制,类型肯定正确,但是推导不出,所以这里直接 as any 屏蔽掉类型推导 + const x0 = params[0] as any; + const x1 = params[1] as any; + const x2 = params[2] as any; + const x3 = params[3] as any; + switch (this.type) { + case UniformType.Uniform1f: + this.gl.uniform1f(this.location, x0); + break; + case UniformType.Uniform1fv: + this.gl.uniform1fv(this.location, x0, x1, x2); + break; + case UniformType.Uniform1i: + this.gl.uniform1i(this.location, x0); + break; + case UniformType.Uniform1iv: + this.gl.uniform1iv(this.location, x0, x1, x2); + break; + case UniformType.Uniform1ui: + this.gl.uniform1ui(this.location, x0); + break; + case UniformType.Uniform1uiv: + this.gl.uniform1uiv(this.location, x0, x1, x2); + break; + case UniformType.Uniform2f: + this.gl.uniform2f(this.location, x0, x1); + break; + case UniformType.Uniform2fv: + this.gl.uniform2fv(this.location, x0, x1, x2); + break; + case UniformType.Uniform2i: + this.gl.uniform2i(this.location, x0, x1); + break; + case UniformType.Uniform2iv: + this.gl.uniform2iv(this.location, x0, x1, x2); + break; + case UniformType.Uniform2ui: + this.gl.uniform2ui(this.location, x0, x1); + break; + case UniformType.Uniform2uiv: + this.gl.uniform2uiv(this.location, x0, x1, x2); + break; + case UniformType.Uniform3f: + this.gl.uniform3f(this.location, x0, x1, x2); + break; + case UniformType.Uniform3fv: + this.gl.uniform3fv(this.location, x0, x1, x2); + break; + case UniformType.Uniform3i: + this.gl.uniform3i(this.location, x0, x1, x2); + break; + case UniformType.Uniform3iv: + this.gl.uniform3iv(this.location, x0, x1, x2); + break; + case UniformType.Uniform3ui: + this.gl.uniform3ui(this.location, x0, x1, x2); + break; + case UniformType.Uniform3uiv: + this.gl.uniform3uiv(this.location, x0, x1, x2); + break; + case UniformType.Uniform4f: + this.gl.uniform4f(this.location, x0, x1, x2, x3); + break; + case UniformType.Uniform4fv: + this.gl.uniform4fv(this.location, x0, x1, x2); + break; + case UniformType.Uniform4i: + this.gl.uniform4i(this.location, x0, x1, x2, x3); + break; + case UniformType.Uniform4iv: + this.gl.uniform4iv(this.location, x0, x1, x2); + break; + case UniformType.Uniform4ui: + this.gl.uniform4ui(this.location, x0, x1, x2, x3); + break; + case UniformType.Uniform4uiv: + this.gl.uniform4uiv(this.location, x0, x1, x2); + break; + } + } +} + +class ShaderAttrib implements IShaderAttrib { + constructor( + readonly type: T, + readonly location: number, + readonly gl: WebGL2RenderingContext, + readonly program: GL2Program + ) {} + + set(...params: AttribSetFn[T]) { + // 因为ts类型推导的限制,类型肯定正确,但是推导不出,所以这里直接 as any 屏蔽掉类型推导 + const x0 = params[0] as any; + const x1 = params[1] as any; + const x2 = params[2] as any; + const x3 = params[3] as any; + switch (this.type) { + case AttribType.Attrib1f: + this.gl.vertexAttrib1f(this.location, x0); + break; + case AttribType.Attrib1fv: + this.gl.vertexAttrib1fv(this.location, x0); + break; + case AttribType.Attrib2f: + this.gl.vertexAttrib2f(this.location, x0, x1); + break; + case AttribType.Attrib2fv: + this.gl.vertexAttrib2fv(this.location, x0); + break; + case AttribType.Attrib3f: + this.gl.vertexAttrib3f(this.location, x0, x1, x2); + break; + case AttribType.Attrib3fv: + this.gl.vertexAttrib3fv(this.location, x0); + break; + case AttribType.Attrib4f: + this.gl.vertexAttrib4f(this.location, x0, x1, x2, x3); + break; + case AttribType.Attrib4fv: + this.gl.vertexAttrib4fv(this.location, x0); + break; + case AttribType.AttribI4i: + this.gl.vertexAttribI4i(this.location, x0, x1, x2, x3); + break; + case AttribType.AttribI4iv: + this.gl.vertexAttribI4iv(this.location, x0); + break; + case AttribType.AttribI4ui: + this.gl.vertexAttribI4ui(this.location, x0, x1, x2, x3); + break; + case AttribType.AttribI4uiv: + this.gl.vertexAttribI4uiv(this.location, x0); + break; + default: { + logger.warn(26); + return; + } + } + } +} + +class ShaderAttribArray implements IShaderAttribArray { + constructor( + readonly data: WebGLBuffer, + readonly location: number, + readonly gl: WebGL2RenderingContext, + readonly program: GL2Program + ) {} + + buffer(data: AllowSharedBufferSource | null, usage: GLenum): void; + buffer( + data: ArrayBufferView, + usage: GLenum, + srcOffset: number, + length?: number + ): void; + buffer(data: any, usage: any, srcOffset?: any, length?: any): void { + const gl = this.gl; + gl.bindBuffer(gl.ARRAY_BUFFER, this.data); + if (typeof srcOffset === 'number') { + gl.bufferData(gl.ARRAY_BUFFER, data, usage, srcOffset, length); + } else { + gl.bufferData(gl.ARRAY_BUFFER, data, usage); + } + } + + sub(dstByteOffset: GLintptr, srcData: AllowSharedBufferSource): void; + sub( + dstByteOffset: GLintptr, + srcData: ArrayBufferView, + srcOffset: number, + length?: GLuint + ): void; + sub(dstOffset: any, data: any, offset?: any, length?: any): void { + const gl = this.gl; + gl.bindBuffer(gl.ARRAY_BUFFER, this.data); + if (typeof offset === 'number') { + gl.bufferSubData(gl.ARRAY_BUFFER, dstOffset, data, offset, length); + } else { + gl.bufferSubData(gl.ARRAY_BUFFER, dstOffset, data); + } + } + + pointer( + p0: GLint, + p1: GLenum, + p2: GLboolean, + p3: GLsizei, + p4: GLintptr + ): void { + const gl = this.gl; + gl.bindBuffer(gl.ARRAY_BUFFER, this.data); + gl.vertexAttribPointer(this.location, p0, p1, p2, p3, p4); + } + + pointerI( + size: GLint, + type: GLenum, + stride: GLsizei, + offset: GLintptr + ): void { + const gl = this.gl; + gl.bindBuffer(gl.ARRAY_BUFFER, this.data); + gl.vertexAttribIPointer(this.location, size, type, stride, offset); + } + + divisor(divisor: number): void { + const gl = this.gl; + gl.vertexAttribDivisor(this.location, divisor); + } + + enable(): void { + this.gl.enableVertexAttribArray(this.location); + } + + disable(): void { + this.gl.disableVertexAttribArray(this.location); + } +} + +class ShaderIndices implements IShaderIndices { + constructor( + readonly data: WebGLBuffer, + readonly gl: WebGL2RenderingContext, + readonly program: GL2Program + ) {} + + buffer(data: AllowSharedBufferSource | null, usage: GLenum): void; + buffer( + data: ArrayBufferView, + usage: GLenum, + srcOffset: number, + length?: number + ): void; + buffer(p0: any, p1: any, p2?: any, p3?: any): void { + const gl = this.gl; + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.data); + if (typeof p2 === 'number') { + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, p0, p1, p2, p3); + } else { + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, p0, p1); + } + } + + sub(dstByteOffset: GLintptr, srcData: AllowSharedBufferSource): void; + sub( + dstByteOffset: GLintptr, + srcData: ArrayBufferView, + srcOffset: number, + length?: GLuint + ): void; + sub(p0: any, p1: any, p2?: any, p3?: any): void { + const gl = this.gl; + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.data); + if (typeof p2 === 'number') { + gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, p0, p1, p2, p3); + } else { + gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, p0, p1); + } + } +} + +class ShaderUniformMatrix implements IShaderUniformMatrix { + constructor( + readonly type: UniformMatrix, + readonly location: WebGLUniformLocation, + readonly gl: WebGL2RenderingContext, + readonly program: GL2Program + ) {} + + set(x2: GLboolean, x3: Float32List, x4?: number, x5?: number): void { + switch (this.type) { + case UniformMatrix.UMatrix2x2: + this.gl.uniformMatrix2fv(this.location, x2, x3, x4, x5); + break; + case UniformMatrix.UMatrix2x3: + this.gl.uniformMatrix2x3fv(this.location, x2, x3, x4, x5); + break; + case UniformMatrix.UMatrix2x4: + this.gl.uniformMatrix2x4fv(this.location, x2, x3, x4, x5); + break; + case UniformMatrix.UMatrix3x2: + this.gl.uniformMatrix3x2fv(this.location, x2, x3, x4, x5); + break; + case UniformMatrix.UMatrix3x3: + this.gl.uniformMatrix3fv(this.location, x2, x3, x4, x5); + break; + case UniformMatrix.UMatrix3x4: + this.gl.uniformMatrix3x4fv(this.location, x2, x3, x4, x5); + break; + case UniformMatrix.UMatrix4x2: + this.gl.uniformMatrix4x2fv(this.location, x2, x3, x4, x5); + break; + case UniformMatrix.UMatrix4x3: + this.gl.uniformMatrix4x3fv(this.location, x2, x3, x4, x5); + break; + case UniformMatrix.UMatrix4x4: + this.gl.uniformMatrix4fv(this.location, x2, x3, x4, x5); + break; + } + } +} + +class ShaderUniformBlock implements IShaderUniformBlock { + constructor( + readonly location: number, + readonly size: number, + readonly buffer: WebGLBuffer, + readonly binding: number, + readonly gl: WebGL2RenderingContext, + readonly program: GL2Program + ) {} + + set(srcData: AllowSharedBufferSource | null): void; + set(srcData: ArrayBufferView, srcOffset: number, length?: number): void; + set(srcData: unknown, srcOffset?: unknown, length?: unknown): void { + const gl = this.gl; + const buffer = this.buffer; + gl.bindBuffer(gl.UNIFORM_BUFFER, buffer); + if (srcOffset !== void 0) { + // @ts-ignore + gl.bufferSubData(gl.UNIFORM_BUFFER, 0, srcData, srcOffset, length); + } else { + // @ts-ignore + gl.bufferSubData(gl.UNIFORM_BUFFER, 0, srcData); + } + gl.bindBufferBase(gl.UNIFORM_BUFFER, this.binding, buffer); + } +} + +class ShaderTexture2D implements IShaderTexture2D { + constructor( + readonly texture: WebGLTexture, + readonly index: number, + readonly uniform: IShaderUniform, + readonly gl: WebGL2RenderingContext, + readonly program: GL2Program, + public width: number = 0, + public height: number = 0 + ) { + uniform.set(index); + } + + set(source: TexImageSource): void { + const gl = this.gl; + gl.activeTexture(gl.TEXTURE0 + this.index); + gl.bindTexture(gl.TEXTURE_2D, this.texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + gl.RGBA, + gl.UNSIGNED_BYTE, + source + ); + if (source instanceof VideoFrame) { + this.width = source.codedWidth; + this.height = source.codedHeight; + } else { + this.width = source.width; + this.height = source.height; + } + } + + sub( + source: TexImageSource, + x: number, + y: number, + width: number, + height: number + ): void { + const gl = this.gl; + gl.activeTexture(gl.TEXTURE0 + this.index); + gl.bindTexture(gl.TEXTURE_2D, this.texture); + + // 进行边界检查,避免超出纹理边界 + if (x + width > this.width || y + height > this.height) { + logger.warn(32); + width = Math.min(width, this.width - x); + height = Math.min(height, this.height - y); + } + + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + x, + y, + width, + height, + gl.RGBA, + gl.UNSIGNED_BYTE, + source + ); + } +} + +interface DrawArraysParam { + mode: GLenum; + first: number; + count: number; +} + +interface DrawElementsParam { + mode: GLenum; + count: number; + type: GLenum; + offset: GLintptr; +} + +interface DrawArraysInstancedParam { + mode: GLenum; + first: number; + count: number; + instanceCount: number; +} + +interface DrawElementsInstancedParam { + mode: GLenum; + count: number; + type: GLenum; + offset: GLintptr; + instanceCount: number; +} + +export interface DrawParamsMap { + [RenderMode.Arrays]: DrawArraysParam; + [RenderMode.ArraysInstanced]: DrawArraysInstancedParam; + [RenderMode.Elements]: DrawElementsParam; + [RenderMode.ElementsInstanced]: DrawElementsInstancedParam; +} + +interface ShaderProgramEvent { + load: []; + unload: []; +} + +export class GL2Program extends EventEmitter { + /** 顶点着色器 */ + private vertex: string = ''; + /** 片元着色器 */ + private fragment: string = ''; + /** webgl2上下文 */ + gl: WebGL2RenderingContext; + /** 当前着色器程序的着色器渲染元素 */ + element: GL2; + + /** uniform存放地址 */ + private uniform: Map> = new Map(); + /** attribute存放地址,300版本里面叫做in */ + private attribute: Map> = new Map(); + /** attribute array 存放地址 */ + private attribArray: Map = new Map(); + /** 顶点索引存放地址 */ + private indices: Map = new Map(); + /** uniform矩阵存放地址 */ + private matrix: Map = new Map(); + /** uniform block 存放地址 */ + private block: Map = new Map(); + /** 纹理存放地址 */ + private texture: Map = new Map(); + /** 当前编译完成的shader程序 */ + private shader: CompiledShader | null = null; + /** 当前的webgl程序 */ + program: WebGLProgram | null = null; + /** 准备函数 */ + private readyFn?: () => boolean; + /** 当前正在使用的顶点索引数组 */ + usingIndices: IShaderIndices | null = null; + + /** 着色器内容是否是默认内容,可以用于优化空着色器 */ + modified: boolean = false; + /** 渲染模式 */ + renderMode: RenderMode = RenderMode.Elements; + + private arraysParams: DrawArraysParam | null = null; + private elementsParams: DrawElementsParam | null = null; + private arraysInstancedParams: DrawArraysInstancedParam | null = null; + private elementsInstancedParams: DrawElementsInstancedParam | null = null; + + /** 是否需要重新编译着色器 */ + protected shaderDirty: boolean = true; + /** 着色器代码的前缀,会在设置时自动添加至代码前 */ + protected readonly prefix: IGL2ProgramPrefix = GL2_PREFIX; + + constructor(shader: GL2, vs?: string, fs?: string) { + super(); + if (vs) this.vs(vs); + if (fs) this.fs(fs); + this.element = shader; + this.gl = shader.gl; + if (vs || fs) this.requestCompile(); + } + + /** + * 使用这个着色器程序时,在渲染之前执行的准备函数 + * @param fn 准备函数,返回 false 时将不执行绘制 + */ + setReady(fn: () => boolean) { + this.readyFn = fn; + } + + /** + * 执行准备函数 + */ + ready(): boolean { + return this.readyFn?.() ?? true; + } + + /** + * 设置渲染模式,目前可选 {@link Shader.DRAW_ARRAYS} 至 {@link Shader.DRAW_INSTANCED} + */ + mode(mode: RenderMode) { + this.renderMode = mode; + } + + /** + * 获取指定渲染模式的渲染参数 + * @param param 渲染模式 + */ + getDrawParams(param: T): DrawParamsMap[T] | null { + switch (param) { + case RenderMode.Arrays: + return this.arraysParams as DrawParamsMap[T]; + case RenderMode.ArraysInstanced: + return this.arraysInstancedParams as DrawParamsMap[T]; + case RenderMode.Elements: + return this.elementsParams as DrawParamsMap[T]; + case RenderMode.ElementsInstanced: + return this.elementsInstancedParams as DrawParamsMap[T]; + } + return null; + } + + /** + * 设置 DRAW_ARRAYS 模式下的渲染参数 + * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/drawArrays + * @param mode 渲染模式 + * @param first 第一个元素的位置 + * @param count 渲染多少个元素 + */ + paramArrays(mode: GLenum, first: number, count: number) { + if (!this.arraysParams) { + this.arraysParams = { mode, first, count }; + } else { + this.arraysParams.mode = mode; + this.arraysParams.first = first; + this.arraysParams.count = count; + } + } + + /** + * 设置 DRAW_ARRAYS_INSTANCED 模式下的渲染参数 + * 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/drawArraysInstanced + * @param mode 渲染模式 + * @param first 第一个元素的位置 + * @param count 渲染多少个元素 + * @param instanceCount 渲染实例数量 + */ + paramArraysInstanced( + mode: GLenum, + first: number, + count: number, + instanceCount: number + ) { + if (!this.arraysInstancedParams) { + this.arraysInstancedParams = { mode, first, count, instanceCount }; + } else { + this.arraysInstancedParams.mode = mode; + this.arraysInstancedParams.first = first; + this.arraysInstancedParams.count = count; + this.arraysInstancedParams.instanceCount = instanceCount; + } + } + + /** + * 设置 DRAW_ELEMENTS 模式下的渲染参数 + * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/drawElements + * @param mode 渲染模式 + * @param count 渲染元素数量 + * @param type 数据类型 + * @param offset 偏移量 + */ + paramElements(mode: GLenum, count: number, type: GLenum, offset: number) { + if (!this.elementsParams) { + this.elementsParams = { mode, count, type, offset }; + } else { + this.elementsParams.mode = mode; + this.elementsParams.count = count; + this.elementsParams.type = type; + this.elementsParams.offset = offset; + } + } + + /** + * 设置 DRAW_ELEMENTS 模式下的渲染参数 + * 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/drawElementsInstanced + * @param mode 渲染模式 + * @param count 渲染元素数量 + * @param type 数据类型 + * @param offset 偏移量 + * @param instanceCount 渲染实例数量 + */ + paramElementsInstanced( + mode: GLenum, + count: number, + type: GLenum, + offset: number, + instanceCount: number + ) { + if (!this.elementsInstancedParams) { + this.elementsInstancedParams = { + mode, + count, + type, + offset, + instanceCount + }; + } else { + this.elementsInstancedParams.mode = mode; + this.elementsInstancedParams.count = count; + this.elementsInstancedParams.type = type; + this.elementsInstancedParams.offset = offset; + this.elementsInstancedParams.instanceCount = instanceCount; + } + } + + /** + * 切换渲染时使用的顶点索引 + * @param name 要使用的顶点索引名称 + */ + useIndices(name: string | IShaderIndices) { + if (typeof name === 'string') { + const indices = this.getIndices(name); + if (!indices) { + logger.warn(30, name); + return; + } + this.usingIndices = indices; + } else { + if ([...this.indices.values()].includes(name)) { + this.usingIndices = name; + } else { + logger.warn(31); + } + } + } + + /** + * 检查当前是否需要重新编译着色器,如果需要,则重新编译 + * @param force 是否强制重新编译 + */ + requestCompile(force: boolean = false): boolean { + if (!force && !this.shaderDirty) return false; + return this.compile(); + } + + /** + * 设置顶点着色器内容 + * @param vs 顶点着色器 + */ + vs(vs: string) { + this.vertex = this.prefix.VERTEX + vs; + this.shaderDirty = true; + this.modified = true; + } + + /** + * 设置片元着色器内容 + * @param fs 片元着色器 + */ + fs(fs: string) { + this.fragment = this.prefix.FRAGMENT + fs; + this.shaderDirty = true; + this.modified = true; + } + + /** + * 当这个程序被卸载时执行的函数 + */ + unload() { + this.attribArray.forEach(v => { + v.disable(); + }); + this.emit('load'); + } + + /** + * 当这个程序被加载(使用)时执行的函数 + */ + load() { + this.attribArray.forEach(v => { + v.enable(); + }); + this.emit('unload'); + } + + /** + * 获取一个uniform,需要事先定义,否则返回null + * @param uniform uniform名称 + */ + getUniform( + uniform: string + ): IShaderUniform | null { + return (this.uniform.get(uniform) as IShaderUniform) ?? null; + } + + /** + * 获取一个attribute,需要事先定义,否则返回null + * @param attrib attribute名称 + */ + getAttribute( + attrib: string + ): IShaderAttrib | null { + return (this.attribute.get(attrib) as IShaderAttrib) ?? null; + } + + /** + * 获取一个attribute array,需要事先定义,否则返回null + * @param name attribute array名称 + */ + getAttribArray(name: string): IShaderAttribArray | null { + return this.attribArray.get(name) ?? null; + } + + /** + * 获取一个顶点索引数组,需要提前定义,否则返回null + * @param name 顶点索引数组的名称 + */ + getIndices(name: string): IShaderIndices | null { + return this.indices.get(name) ?? null; + } + + /** + * 获取一个 uniform matrix,需要事先定义,否则返回null + * @param matrix uniform matrix 的名称 + */ + getMatrix(matrix: string): IShaderUniformMatrix | null { + return this.matrix.get(matrix) ?? null; + } + + /** + * 获取一个 uniform block,例如 UBO,需要事先定义,否则返回null + * @param block uniform block 的名称 + */ + getUniformBlock(block: string): IShaderUniformBlock | null { + return this.block.get(block) ?? null; + } + + /** + * 获取一个 texture,需要事先定义,否则返回null + * @param name texture 的名称 + */ + getTexture(name: string): IShaderTexture2D | null { + return this.texture.get(name) ?? null; + } + + /** + * 定义一个 uniform 变量,并存入本着色器程序的 uniform 变量映射 + * @param uniform uniform 变量名 + * @param type uniform 类型,可选 {@link Shader.UNIFORM_1f} 至 {@link Shader.UNIFORM_4uiv} + * @returns uniform 变量的操作对象,可用于设置其值 + */ + defineUniform( + uniform: string, + type: T + ): IShaderUniform | null { + const u = this.getUniform(uniform); + if (u) { + if (u.type === type) return u; + else { + logger.warn(28, 'uniform', uniform); + return null; + } + } + const program = this.program; + const gl = this.element.gl; + if (!program || !gl) return null; + const location = gl.getUniformLocation(program, uniform); + if (!location) return null; + const obj = new ShaderUniform(type, location, gl, this); + this.uniform.set(uniform, obj); + return obj; + } + + /** + * 定义一个 uniform 矩阵变量,并存入本着色器程序的 uniform 矩阵变量映射 + * @param uniform uniform 矩阵变量名 + * @param type uniform 矩阵类型,可选 {@link Shader.U_MATRIX_2x2} 至 {@link Shader.U_MATRIX_4x4} + * @returns uniform 矩阵变量的操作对象,可用于设置其值 + */ + defineUniformMatrix( + uniform: string, + type: UniformMatrix + ): IShaderUniformMatrix | null { + const u = this.getMatrix(uniform); + if (u) { + if (u.type === type) return u; + else { + logger.warn(28, 'uniform matrix', uniform); + return null; + } + } + const program = this.program; + const gl = this.element.gl; + if (!program || !gl) return null; + const location = gl.getUniformLocation(program, uniform); + if (!location) return null; + const obj = new ShaderUniformMatrix(type, location, gl, this); + this.matrix.set(uniform, obj); + return obj; + } + + /** + * 定义一个 attribute 常量,并存入本着色器程序的 attribute 常量映射,在 es 300 版本中叫做 in + * @param attrib attribute 常量名 + * @param type attribute 类型,可选 {@link Shader.Attrib1f} 至 {@link Shader.AttribI4uiv} + * @returns attribute 常量的操作对象,可用于设置其值 + */ + defineAttribute( + attrib: string, + type: T + ): IShaderAttrib | null { + const u = this.getAttribute(attrib); + if (u) { + if (u.type === type) return u; + else { + logger.warn(28, 'attribute', attrib); + return null; + } + } + const program = this.program; + const gl = this.element.gl; + if (!program || !gl) return null; + const location = gl.getAttribLocation(program, attrib); + if (location === -1) return null; + const obj = new ShaderAttrib(type, location, gl, this); + this.attribute.set(attrib, obj); + return obj; + } + + /** + * 定义一个顶点数组 + * @param name 顶点数组名称 + */ + defineAttribArray(name: string) { + const u = this.getAttribArray(name); + if (u) return u; + const program = this.program; + const gl = this.element.gl; + if (!program || !gl) return null; + const buffer = gl.createBuffer(); + if (!buffer) return null; + const location = gl.getAttribLocation(program, name); + if (location === -1) return null; + const obj = new ShaderAttribArray(buffer, location, gl, this); + this.attribArray.set(name, obj); + return obj; + } + + /** + * 定义一个顶点索引数组 + * @param name 顶点索引数组的名称 + */ + defineIndices(name: string) { + const u = this.getIndices(name); + if (u) return u; + const program = this.program; + const gl = this.element.gl; + if (!program || !gl) return null; + const buffer = gl.createBuffer(); + if (!buffer) return null; + const obj = new ShaderIndices(buffer, gl, this); + this.indices.set(name, obj); + return obj; + } + + /** + * 定义一个 uniform block,例如 UBO,并存入本着色器程序的 uniform block 映射 + * 用于一次性向着色器传输大量数据 + * @param block uniform block 名称 + * @param size 数据量,即数据长度,例如一个vec4就是4个长度 + * @param usage 缓冲区用途,例如 gl.STATIC_DRAW 是指会频繁读取但不会频繁写入 + * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/bufferData + * 的 `usage` 参数 + * @param binding uniform block 的索引,例如这是你设置的第一个uniform block,就可以填0,第二个填1,以此类推 + * @returns uniform block 的操作对象,可用于设置其值 + */ + defineUniformBlock( + block: string, + size: number, + usage: number, + binding: number + ): IShaderUniformBlock | null { + const u = this.getUniformBlock(block); + if (u) { + if (u.size === size) return u; + else { + logger.warn(28, 'uniform block', block); + return null; + } + } + const program = this.program; + const gl = this.element.gl; + if (!program || !gl) return null; + const loc = gl.getUniformBlockIndex(program, block); + if (loc === -1) return null; + const buf = gl.createBuffer(); + if (!buf) return null; + const data = new Float32Array(size); + data.fill(0); + gl.bindBuffer(gl.UNIFORM_BUFFER, buf); + gl.bufferData(gl.UNIFORM_BUFFER, data, usage); + gl.uniformBlockBinding(program, loc, binding); + gl.bindBufferBase(gl.UNIFORM_BUFFER, binding, buf); + const obj = new ShaderUniformBlock(loc, size, buf, binding, gl, this); + this.block.set(block, obj); + return obj; + } + + /** + * 定义一个材质 + * @param name 纹理名称 + * @param index 纹理索引,根据不同浏览器,其最大数量不一定相等,根据标准其数量应该大于等于 8 个, + * 因此考虑到兼容性,不建议纹理数量超过 8 个。 + * @param w 纹理的宽度 + * @param h 纹理的高度 + * @returns 这个 texture 的操作对象,可以用于设置其内容 + */ + defineTexture( + name: string, + index: number, + w?: number, + h?: number + ): IShaderTexture2D | null { + const u = this.getTexture(name); + if (u) { + if (u.index === index) return u; + else { + logger.warn(28, 'texture', name); + return null; + } + } + if (index > this.element.MAX_TEXTURE_COUNT) { + logger.warn(29); + return null; + } + const uni = this.defineUniform(name, UniformType.Uniform1i); + if (!uni) return null; + const program = this.program; + const gl = this.element.gl; + if (!program || !gl) return null; + const tex = gl.createTexture(); + if (!tex) return null; + const obj = new ShaderTexture2D(tex, index, uni, gl, this, w, h); + this.texture.set(name, obj); + return obj; + } + + /** + * 摧毁这个着色器程序,不要直接调用,请使用 {@link Shader.deleteProgram} 来删除一个着色器程序 + */ + destroy() { + this.clearProgram(); + } + + private clearProgram() { + if (!this.gl) return; + this.uniform.clear(); + this.attribute.clear(); + this.matrix.clear(); + this.gl.deleteProgram(this.program); + if (this.shader) { + this.gl.deleteShader(this.shader.vertex); + this.gl.deleteShader(this.shader.fragment); + } + this.block.forEach(v => { + this.gl.deleteBuffer(v.buffer); + }); + this.attribArray.forEach(v => { + this.gl.deleteBuffer(v.data); + }); + this.texture.forEach(v => { + this.gl.deleteTexture(v.texture); + }); + this.indices.forEach(v => { + this.gl.deleteBuffer(v.data); + }); + this.texture.clear(); + this.indices.clear(); + this.attribArray.clear(); + this.block.clear(); + } + + protected compile() { + this.shaderDirty = false; + this.clearProgram(); + + const shader = this.element; + const gl = shader.gl; + if (!gl) return false; + + const program = gl.createProgram(); + if (!program) return false; + + const vs = this.compileShader(gl.VERTEX_SHADER, this.vertex); + const fs = this.compileShader(gl.FRAGMENT_SHADER, this.fragment); + + if (!vs || !fs) return false; + + gl.attachShader(program, vs); + gl.attachShader(program, fs); + gl.linkProgram(program); + gl.useProgram(program); + + this.program = program; + this.shader = { vertex: vs, fragment: fs }; + return true; + } + + private compileShader(type: number, source: string): WebGLShader | null { + const gl = this.element.gl; + const shader = gl.createShader(type); + if (!shader) return null; + gl.shaderSource(shader, source); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + logger.error( + 13, + type === gl.VERTEX_SHADER ? 'vertex' : 'fragment', + gl.getShaderInfoLog(shader) ?? '' + ); + } + + return shader; + } +} diff --git a/src/core/render/shader.ts b/src/core/render/shader.ts index def216a..c30b88b 100644 --- a/src/core/render/shader.ts +++ b/src/core/render/shader.ts @@ -1,42 +1,25 @@ -import EventEmitter from 'eventemitter3'; -import { logger } from '../common/logger'; import { MotaOffscreenCanvas2D } from '../fx/canvas2d'; -import { isWebGL2Supported } from '../fx/webgl'; -import { Container } from './container'; import { ERenderItemEvent, RenderItem, RenderItemPosition } from './item'; import { Transform } from './transform'; +import { GL2, GL2Program, IGL2ProgramPrefix } from './gl2'; -const SHADER_VERTEX_PREFIX_300 = /* glsl */ `#version 300 es +const SHADER_PREFIX: IGL2ProgramPrefix = { + VERTEX: /* glsl */ `#version 300 es precision highp float; in vec4 a_position; in vec2 a_texCoord; out vec2 v_texCoord; -`; -const SHADER_VERTEX_PREFIX_100 = /* glsl */ ` -precision highp float; - -attribute vec4 a_position; -attribute vec2 a_texCoord; - -varying vec2 v_texCoord; -`; - -const SHADER_FRAGMENT_PREFIX_300 = /* glsl */ `#version 300 es +`, + FRAGMENT: /* glsl */ `#version 300 es precision highp float; in vec2 v_texCoord; uniform sampler2D u_sampler; -`; -const SHADER_FRAGMENT_PREFIX_100 = /* glsl */ ` -precision highp float; - -varying vec2 v_texCoord; - -uniform sampler2D u_sampler; -`; +` +}; const DEFAULT_VS = /* glsl */ ` void main() { @@ -50,205 +33,7 @@ void main() { } `; -interface CompiledShader { - vertex: WebGLShader; - fragment: WebGLShader; -} - -const enum ShaderVersion { - ES_100, - ES_300 -} - -const enum RenderMode { - Arrays, - Elements, - ArraysInstanced, - ElementsInstanced -} - -export const enum UniformType { - Uniform1f, - Uniform1fv, - Uniform1i, - Uniform1iv, - Uniform1ui, - Uniform1uiv, - Uniform2f, - Uniform2fv, - Uniform2i, - Uniform2iv, - Uniform2ui, - Uniform2uiv, - Uniform3f, - Uniform3fv, - Uniform3i, - Uniform3iv, - Uniform3ui, - Uniform3uiv, - Uniform4f, - Uniform4fv, - Uniform4i, - Uniform4iv, - Uniform4ui, - Uniform4uiv -} - -export const enum UniformMatrix { - UMatrix2x2, - UMatrix2x3, - UMatrix2x4, - UMatrix3x2, - UMatrix3x3, - UMatrix3x4, - UMatrix4x2, - UMatrix4x3, - UMatrix4x4 -} - -export const enum AttribType { - Attrib1f, - Attrib1fv, - Attrib2f, - Attrib2fv, - Attrib3f, - Attrib3fv, - Attrib4f, - Attrib4fv, - AttribI4i, - AttribI4iv, - AttribI4ui, - AttribI4uiv -} - -interface EShaderEvent extends ERenderItemEvent {} - -export class Shader extends Container { - /** 是否支持此组件 */ - static readonly support: boolean = isWebGL2Supported(); - - // 会用到的一些常量 - // 着色器版本 - readonly VERSION_ES_100: ShaderVersion.ES_100 = 0; - readonly VERSION_ES_300: ShaderVersion.ES_300 = 1; - // uniform 类型 - readonly UNIFORM_1f: UniformType.Uniform1f = UniformType.Uniform1f; - readonly UNIFORM_1fv: UniformType.Uniform1fv = UniformType.Uniform1fv; - readonly UNIFORM_1i: UniformType.Uniform1i = UniformType.Uniform1i; - readonly UNIFORM_1iv: UniformType.Uniform1iv = UniformType.Uniform1iv; - readonly UNIFORM_1ui: UniformType.Uniform1ui = UniformType.Uniform1ui; - readonly UNIFORM_1uiv: UniformType.Uniform1uiv = UniformType.Uniform1uiv; - readonly UNIFORM_2f: UniformType.Uniform2f = UniformType.Uniform2f; - readonly UNIFORM_2fv: UniformType.Uniform2fv = UniformType.Uniform2fv; - readonly UNIFORM_2i: UniformType.Uniform2i = UniformType.Uniform2i; - readonly UNIFORM_2iv: UniformType.Uniform2iv = UniformType.Uniform2iv; - readonly UNIFORM_2ui: UniformType.Uniform2ui = UniformType.Uniform2ui; - readonly UNIFORM_2uiv: UniformType.Uniform2uiv = UniformType.Uniform2uiv; - readonly UNIFORM_3f: UniformType.Uniform3f = UniformType.Uniform3f; - readonly UNIFORM_3fv: UniformType.Uniform3fv = UniformType.Uniform3fv; - readonly UNIFORM_3i: UniformType.Uniform3i = UniformType.Uniform3i; - readonly UNIFORM_3iv: UniformType.Uniform3iv = UniformType.Uniform3iv; - readonly UNIFORM_3ui: UniformType.Uniform3ui = UniformType.Uniform3ui; - readonly UNIFORM_3uiv: UniformType.Uniform3uiv = UniformType.Uniform3uiv; - readonly UNIFORM_4f: UniformType.Uniform4f = UniformType.Uniform4f; - readonly UNIFORM_4fv: UniformType.Uniform4fv = UniformType.Uniform4fv; - readonly UNIFORM_4i: UniformType.Uniform4i = UniformType.Uniform4i; - readonly UNIFORM_4iv: UniformType.Uniform4iv = UniformType.Uniform4iv; - readonly UNIFORM_4ui: UniformType.Uniform4ui = UniformType.Uniform4ui; - readonly UNIFORM_4uiv: UniformType.Uniform4uiv = UniformType.Uniform4uiv; - // uniform matrix 类型 - readonly U_MATRIX_2x2: UniformMatrix.UMatrix2x2 = UniformMatrix.UMatrix2x2; - readonly U_MATRIX_2x3: UniformMatrix.UMatrix2x3 = UniformMatrix.UMatrix2x3; - readonly U_MATRIX_2x4: UniformMatrix.UMatrix2x4 = UniformMatrix.UMatrix2x4; - readonly U_MATRIX_3x2: UniformMatrix.UMatrix3x2 = UniformMatrix.UMatrix3x2; - readonly U_MATRIX_3x3: UniformMatrix.UMatrix3x3 = UniformMatrix.UMatrix3x3; - readonly U_MATRIX_3x4: UniformMatrix.UMatrix3x4 = UniformMatrix.UMatrix3x4; - readonly U_MATRIX_4x2: UniformMatrix.UMatrix4x2 = UniformMatrix.UMatrix4x2; - readonly U_MATRIX_4x3: UniformMatrix.UMatrix4x3 = UniformMatrix.UMatrix4x3; - readonly U_MATRIX_4x4: UniformMatrix.UMatrix4x4 = UniformMatrix.UMatrix4x4; - // attribute 类型 - readonly ATTRIB_1f: AttribType.Attrib1f = AttribType.Attrib1f; - readonly ATTRIB_1fv: AttribType.Attrib1fv = AttribType.Attrib1fv; - readonly ATTRIB_2f: AttribType.Attrib2f = AttribType.Attrib2f; - readonly ATTRIB_2fv: AttribType.Attrib2fv = AttribType.Attrib2fv; - readonly ATTRIB_3f: AttribType.Attrib3f = AttribType.Attrib3f; - readonly ATTRIB_3fv: AttribType.Attrib3fv = AttribType.Attrib3fv; - readonly ATTRIB_4f: AttribType.Attrib4f = AttribType.Attrib4f; - readonly ATTRIB_4fv: AttribType.Attrib4fv = AttribType.Attrib4fv; - readonly ATTRIB_I4i: AttribType.AttribI4i = AttribType.AttribI4i; - readonly ATTRIB_I4iv: AttribType.AttribI4iv = AttribType.AttribI4iv; - readonly ATTRIB_I4ui: AttribType.AttribI4ui = AttribType.AttribI4ui; - readonly ATTRIB_I4uiv: AttribType.AttribI4uiv = AttribType.AttribI4uiv; - // 渲染模式 - readonly DRAW_ARRAYS = RenderMode.Arrays; - readonly DRAW_ELEMENTS = RenderMode.Elements; - readonly DRAW_ARRAYS_INSTANCED = RenderMode.ArraysInstanced; - readonly DRAW_ELEMENTS_INSTANCED = RenderMode.ElementsInstanced; - // 其他常量 - readonly MAX_TEXTURE_COUNT: number = 0; - - canvas: HTMLCanvasElement; - gl: WebGL2RenderingContext; - - /** 是否需要重新渲染着色器 */ - private shaderRenderDirty: boolean = true; - - /** webgl使用的程序 */ - private program: ShaderProgram | null = null; - - /** 当前渲染实例的所有着色器程序 */ - private programs: Set = new Set(); - /** framebuffer 映射 */ - private framebufferMap: Map = new Map(); - - constructor(type: RenderItemPosition = 'static') { - super(type, !Shader.support); - - this.canvas = document.createElement('canvas'); - this.gl = this.canvas.getContext('webgl2')!; - if (!Shader.support) { - this.canvas.width = 0; - this.canvas.height = 0; - } else { - const num = this.gl.getParameter(this.gl.MAX_TEXTURE_IMAGE_UNITS); - if (typeof num === 'number') { - this.MAX_TEXTURE_COUNT = num; - } - } - - this.init(); - } - - protected render( - canvas: MotaOffscreenCanvas2D, - transform: Transform - ): void { - if (!Shader.support || !this.program || !this.program.modified) { - super.render(canvas, transform); - } else { - const compile = this.program.requestCompile(); - if (compile) { - this.gl.useProgram(this.program.program); - } - - if (this.cacheDirty) { - const { ctx } = this.cache; - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.save(); - super.render(this.cache, transform); - ctx.restore(); - this.cacheDirty = false; - } - - if (this.shaderRenderDirty) { - this.drawScene(); - this.shaderRenderDirty = false; - } - - canvas.ctx.drawImage(this.canvas, 0, 0, this.width, this.height); - } - } - +export class Shader extends GL2 { setHD(hd: boolean): void { super.setHD(hd); this.sizeGL(this.width, this.height); @@ -264,1623 +49,451 @@ export class Shader extends Container { const scale = ratio * core.domStyle.scale; this.canvas.width = width * scale; this.canvas.height = height * scale; - this.shaderRenderDirty = true; } - update(item?: RenderItem): void { - super.update(item); - this.shaderRenderDirty = true; - } - - drawScene() { - const gl = this.gl; - const program = this.program; - if (!gl || !program) return; - const useDefault = program.defaultReady; - const dr = useDefault ? this.defaultReady() : true; - const ready = dr && program.ready(); - if (!ready) return; - const indices = program.usingIndices; - const param = program.getDrawParams(program.renderMode); - if (!param) return; - - // 清空画布 - gl.viewport(0, 0, this.canvas.width, this.canvas.height); - gl.clearColor(0, 0, 0, 0); - gl.clearDepth(1); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - - const pre = this.preDraw(gl, program, param, indices); - if (!pre) { - this.postDraw(gl, program, param, indices); - return; - } - - this.draw(gl, program, param, indices); - - this.postDraw(gl, program, param, indices); - } - - draw( - gl: WebGL2RenderingContext, - program: ShaderProgram, - param: DrawParamsMap[keyof DrawParamsMap], - indices: IShaderIndices | null - ) { - switch (program.renderMode) { - case RenderMode.Arrays: { - const { mode, first, count } = param as DrawArraysParam; - gl.drawArrays(mode, first, count); - } - case RenderMode.Elements: { - if (!indices) return; - const { mode, count, type, offset } = - param as DrawElementsParam; - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices.data); - gl.drawElements(mode, count, type, offset); - } - case RenderMode.ArraysInstanced: { - const { mode, first, count, instanceCount } = - param as DrawArraysInstancedParam; - gl.drawArraysInstanced(mode, first, count, instanceCount); - } - case RenderMode.ElementsInstanced: { - if (!indices) return; - const { - mode, - count, - type, - offset, - instanceCount: ins - } = param as DrawElementsInstancedParam; - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices.data); - gl.drawElementsInstanced(mode, count, type, offset, ins); - } - } - } - - /** - * 在本着色器内部渲染之前执行的渲染,如果返回false,则表示不进行内部渲染,但依然会执行 {@link postDraw}。 - * 继承本类,并复写此方法即可实现前置渲染功能 - */ protected preDraw( + canvas: MotaOffscreenCanvas2D, + transform: Transform, gl: WebGL2RenderingContext, - program: ShaderProgram, - param: DrawParamsMap[keyof DrawParamsMap], - indices: IShaderIndices | null + program: GL2Program ): boolean { - return true; - } - - /** - * 在本着色器内部渲染之后执行的渲染,即使preDraw返回false,本函数也会执行 - * 继承本类,并复写此方法即可实现后置渲染功能 - */ - protected postDraw( - gl: WebGL2RenderingContext, - program: ShaderProgram, - param: DrawParamsMap[keyof DrawParamsMap], - indices: IShaderIndices | null - ) {} - - /** - * 默认的准备函数 - * @returns 是否准备成功 - */ - protected defaultReady(): boolean { - const program = this.program; - if (!program) return false; + if (!program.modified) return false; const tex = program.getTexture('u_sampler'); if (!tex) return false; - const canvas = this.cache.canvas; - if (tex.width === canvas.width && tex.height === canvas.height) { - tex.sub(canvas, 0, 0, canvas.width, canvas.height); + const c = canvas.canvas; + if (tex.width === c.width && tex.height === c.height) { + tex.sub(c, 0, 0, c.width, c.height); } else { - tex.set(canvas); + tex.set(c); } return true; } - /** - * 将画面渲染至帧缓冲 - * @param name 帧缓冲名称 - * @param texture 渲染至的纹理 - * @param clear 是否先清空画布再渲染 - */ - framebuffer( - name: string, - texture: IShaderTexture2D, - clear: boolean = true - ) { - const gl = this.gl; - const buffer = this.framebufferMap.get(name); - const program = this.program; - if (!gl || !buffer || !program) return; - const indices = program.usingIndices; - if (!indices) return; - const param = program.getDrawParams(program.renderMode); - if (!param) return; + protected postDraw( + canvas: MotaOffscreenCanvas2D, + transform: Transform, + gl: WebGL2RenderingContext, + program: GL2Program + ): void {} +} - const tex = texture.texture; - gl.bindTexture(gl.TEXTURE_2D, tex); - gl.bindFramebuffer(gl.FRAMEBUFFER, buffer); - if (clear) { - gl.viewport(0, 0, this.canvas.width, this.canvas.height); - gl.clearColor(0, 0, 0, 0); - gl.clearDepth(1); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); +// export class Shader extends Container { +// /** 是否支持此组件 */ +// static readonly support: boolean = isWebGL2Supported(); + +// // 会用到的一些常量 +// // 着色器版本 +// readonly VERSION_ES_100: ShaderVersion.ES_100 = 0; +// readonly VERSION_ES_300: ShaderVersion.ES_300 = 1; +// // uniform 类型 +// readonly UNIFORM_1f: UniformType.Uniform1f = UniformType.Uniform1f; +// readonly UNIFORM_1fv: UniformType.Uniform1fv = UniformType.Uniform1fv; +// readonly UNIFORM_1i: UniformType.Uniform1i = UniformType.Uniform1i; +// readonly UNIFORM_1iv: UniformType.Uniform1iv = UniformType.Uniform1iv; +// readonly UNIFORM_1ui: UniformType.Uniform1ui = UniformType.Uniform1ui; +// readonly UNIFORM_1uiv: UniformType.Uniform1uiv = UniformType.Uniform1uiv; +// readonly UNIFORM_2f: UniformType.Uniform2f = UniformType.Uniform2f; +// readonly UNIFORM_2fv: UniformType.Uniform2fv = UniformType.Uniform2fv; +// readonly UNIFORM_2i: UniformType.Uniform2i = UniformType.Uniform2i; +// readonly UNIFORM_2iv: UniformType.Uniform2iv = UniformType.Uniform2iv; +// readonly UNIFORM_2ui: UniformType.Uniform2ui = UniformType.Uniform2ui; +// readonly UNIFORM_2uiv: UniformType.Uniform2uiv = UniformType.Uniform2uiv; +// readonly UNIFORM_3f: UniformType.Uniform3f = UniformType.Uniform3f; +// readonly UNIFORM_3fv: UniformType.Uniform3fv = UniformType.Uniform3fv; +// readonly UNIFORM_3i: UniformType.Uniform3i = UniformType.Uniform3i; +// readonly UNIFORM_3iv: UniformType.Uniform3iv = UniformType.Uniform3iv; +// readonly UNIFORM_3ui: UniformType.Uniform3ui = UniformType.Uniform3ui; +// readonly UNIFORM_3uiv: UniformType.Uniform3uiv = UniformType.Uniform3uiv; +// readonly UNIFORM_4f: UniformType.Uniform4f = UniformType.Uniform4f; +// readonly UNIFORM_4fv: UniformType.Uniform4fv = UniformType.Uniform4fv; +// readonly UNIFORM_4i: UniformType.Uniform4i = UniformType.Uniform4i; +// readonly UNIFORM_4iv: UniformType.Uniform4iv = UniformType.Uniform4iv; +// readonly UNIFORM_4ui: UniformType.Uniform4ui = UniformType.Uniform4ui; +// readonly UNIFORM_4uiv: UniformType.Uniform4uiv = UniformType.Uniform4uiv; +// // uniform matrix 类型 +// readonly U_MATRIX_2x2: UniformMatrix.UMatrix2x2 = UniformMatrix.UMatrix2x2; +// readonly U_MATRIX_2x3: UniformMatrix.UMatrix2x3 = UniformMatrix.UMatrix2x3; +// readonly U_MATRIX_2x4: UniformMatrix.UMatrix2x4 = UniformMatrix.UMatrix2x4; +// readonly U_MATRIX_3x2: UniformMatrix.UMatrix3x2 = UniformMatrix.UMatrix3x2; +// readonly U_MATRIX_3x3: UniformMatrix.UMatrix3x3 = UniformMatrix.UMatrix3x3; +// readonly U_MATRIX_3x4: UniformMatrix.UMatrix3x4 = UniformMatrix.UMatrix3x4; +// readonly U_MATRIX_4x2: UniformMatrix.UMatrix4x2 = UniformMatrix.UMatrix4x2; +// readonly U_MATRIX_4x3: UniformMatrix.UMatrix4x3 = UniformMatrix.UMatrix4x3; +// readonly U_MATRIX_4x4: UniformMatrix.UMatrix4x4 = UniformMatrix.UMatrix4x4; +// // attribute 类型 +// readonly ATTRIB_1f: AttribType.Attrib1f = AttribType.Attrib1f; +// readonly ATTRIB_1fv: AttribType.Attrib1fv = AttribType.Attrib1fv; +// readonly ATTRIB_2f: AttribType.Attrib2f = AttribType.Attrib2f; +// readonly ATTRIB_2fv: AttribType.Attrib2fv = AttribType.Attrib2fv; +// readonly ATTRIB_3f: AttribType.Attrib3f = AttribType.Attrib3f; +// readonly ATTRIB_3fv: AttribType.Attrib3fv = AttribType.Attrib3fv; +// readonly ATTRIB_4f: AttribType.Attrib4f = AttribType.Attrib4f; +// readonly ATTRIB_4fv: AttribType.Attrib4fv = AttribType.Attrib4fv; +// readonly ATTRIB_I4i: AttribType.AttribI4i = AttribType.AttribI4i; +// readonly ATTRIB_I4iv: AttribType.AttribI4iv = AttribType.AttribI4iv; +// readonly ATTRIB_I4ui: AttribType.AttribI4ui = AttribType.AttribI4ui; +// readonly ATTRIB_I4uiv: AttribType.AttribI4uiv = AttribType.AttribI4uiv; +// // 渲染模式 +// readonly DRAW_ARRAYS = RenderMode.Arrays; +// readonly DRAW_ELEMENTS = RenderMode.Elements; +// readonly DRAW_ARRAYS_INSTANCED = RenderMode.ArraysInstanced; +// readonly DRAW_ELEMENTS_INSTANCED = RenderMode.ElementsInstanced; +// // 其他常量 +// readonly MAX_TEXTURE_COUNT: number = 0; + +// canvas: HTMLCanvasElement; +// gl: WebGL2RenderingContext; + +// /** 是否需要重新渲染着色器 */ +// private shaderRenderDirty: boolean = true; + +// /** webgl使用的程序 */ +// private program: ShaderProgram | null = null; + +// /** 当前渲染实例的所有着色器程序 */ +// private programs: Set = new Set(); +// /** framebuffer 映射 */ +// private framebufferMap: Map = new Map(); + +// constructor(type: RenderItemPosition = 'static') { +// super(type, !Shader.support); + +// this.canvas = document.createElement('canvas'); +// this.gl = this.canvas.getContext('webgl2')!; +// if (!Shader.support) { +// this.canvas.width = 0; +// this.canvas.height = 0; +// } else { +// const num = this.gl.getParameter(this.gl.MAX_TEXTURE_IMAGE_UNITS); +// if (typeof num === 'number') { +// this.MAX_TEXTURE_COUNT = num; +// } +// } + +// this.init(); +// } + +// protected render( +// canvas: MotaOffscreenCanvas2D, +// transform: Transform +// ): void { +// if (!Shader.support || !this.program || !this.program.modified) { +// super.render(canvas, transform); +// } else { +// const compile = this.program.requestCompile(); +// if (compile) { +// this.gl.useProgram(this.program.program); +// } + +// if (this.cacheDirty) { +// const { ctx } = this.cache; +// ctx.clearRect(0, 0, canvas.width, canvas.height); +// ctx.save(); +// super.render(this.cache, transform); +// ctx.restore(); +// this.cacheDirty = false; +// } + +// if (this.shaderRenderDirty) { +// this.drawScene(); +// this.shaderRenderDirty = false; +// } + +// canvas.ctx.drawImage(this.canvas, 0, 0, this.width, this.height); +// } +// } + +// setHD(hd: boolean): void { +// super.setHD(hd); +// this.sizeGL(this.width, this.height); +// } + +// size(width: number, height: number): void { +// super.size(width, height); +// this.sizeGL(width, height); +// } + +// private sizeGL(width: number, height: number) { +// const ratio = this.highResolution ? devicePixelRatio : 1; +// const scale = ratio * core.domStyle.scale; +// this.canvas.width = width * scale; +// this.canvas.height = height * scale; +// this.shaderRenderDirty = true; +// } + +// update(item?: RenderItem): void { +// super.update(item); +// this.shaderRenderDirty = true; +// } + +// drawScene() { +// const gl = this.gl; +// const program = this.program; +// if (!gl || !program) return; +// const useDefault = program.defaultReady; +// const dr = useDefault ? this.defaultReady() : true; +// const ready = dr && program.ready(); +// if (!ready) return; +// const indices = program.usingIndices; +// const param = program.getDrawParams(program.renderMode); +// if (!param) return; + +// // 清空画布 +// gl.viewport(0, 0, this.canvas.width, this.canvas.height); +// gl.clearColor(0, 0, 0, 0); +// gl.clearDepth(1); +// gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + +// const pre = this.preDraw(gl, program, param, indices); +// if (!pre) { +// this.postDraw(gl, program, param, indices); +// return; +// } + +// this.draw(gl, program, param, indices); + +// this.postDraw(gl, program, param, indices); +// } + +// draw( +// gl: WebGL2RenderingContext, +// program: ShaderProgram, +// param: DrawParamsMap[keyof DrawParamsMap], +// indices: IShaderIndices | null +// ) { +// switch (program.renderMode) { +// case RenderMode.Arrays: { +// const { mode, first, count } = param as DrawArraysParam; +// gl.drawArrays(mode, first, count); +// } +// case RenderMode.Elements: { +// if (!indices) return; +// const { mode, count, type, offset } = +// param as DrawElementsParam; +// gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices.data); +// gl.drawElements(mode, count, type, offset); +// } +// case RenderMode.ArraysInstanced: { +// const { mode, first, count, instanceCount } = +// param as DrawArraysInstancedParam; +// gl.drawArraysInstanced(mode, first, count, instanceCount); +// } +// case RenderMode.ElementsInstanced: { +// if (!indices) return; +// const { +// mode, +// count, +// type, +// offset, +// instanceCount: ins +// } = param as DrawElementsInstancedParam; +// gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices.data); +// gl.drawElementsInstanced(mode, count, type, offset, ins); +// } +// } +// } + +// /** +// * 在本着色器内部渲染之前执行的渲染,如果返回false,则表示不进行内部渲染,但依然会执行 {@link postDraw}。 +// * 继承本类,并复写此方法即可实现前置渲染功能 +// */ +// protected preDraw( +// gl: WebGL2RenderingContext, +// program: ShaderProgram, +// param: DrawParamsMap[keyof DrawParamsMap], +// indices: IShaderIndices | null +// ): boolean { +// return true; +// } + +// /** +// * 在本着色器内部渲染之后执行的渲染,即使preDraw返回false,本函数也会执行 +// * 继承本类,并复写此方法即可实现后置渲染功能 +// */ +// protected postDraw( +// gl: WebGL2RenderingContext, +// program: ShaderProgram, +// param: DrawParamsMap[keyof DrawParamsMap], +// indices: IShaderIndices | null +// ) {} + +// /** +// * 默认的准备函数 +// * @returns 是否准备成功 +// */ +// protected defaultReady(): boolean { +// const program = this.program; +// if (!program) return false; +// const tex = program.getTexture('u_sampler'); +// if (!tex) return false; +// const canvas = this.cache.canvas; +// if (tex.width === canvas.width && tex.height === canvas.height) { +// tex.sub(canvas, 0, 0, canvas.width, canvas.height); +// } else { +// tex.set(canvas); +// } +// return true; +// } + +// /** +// * 将画面渲染至帧缓冲 +// * @param name 帧缓冲名称 +// * @param texture 渲染至的纹理 +// * @param clear 是否先清空画布再渲染 +// */ +// framebuffer( +// name: string, +// texture: IShaderTexture2D, +// clear: boolean = true +// ) { +// const gl = this.gl; +// const buffer = this.framebufferMap.get(name); +// const program = this.program; +// if (!gl || !buffer || !program) return; +// const indices = program.usingIndices; +// if (!indices) return; +// const param = program.getDrawParams(program.renderMode); +// if (!param) return; + +// const tex = texture.texture; +// gl.bindTexture(gl.TEXTURE_2D, tex); +// gl.bindFramebuffer(gl.FRAMEBUFFER, buffer); +// if (clear) { +// gl.viewport(0, 0, this.canvas.width, this.canvas.height); +// gl.clearColor(0, 0, 0, 0); +// gl.clearDepth(1); +// gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); +// } +// gl.framebufferTexture2D( +// gl.FRAMEBUFFER, +// gl.COLOR_ATTACHMENT0, +// gl.TEXTURE_2D, +// tex, +// 0 +// ); +// this.draw(gl, program, param, indices); +// gl.bindFramebuffer(gl.FRAMEBUFFER, null); +// gl.bindTexture(gl.TEXTURE_2D, null); +// } + +// /** +// * 创建一个帧缓冲对象 +// * @param name 帧缓冲名称 +// * @returns 是否创建成功 +// */ +// createFramebuffer(name: string): boolean { +// const gl = this.gl; +// if (!gl) return false; +// const buffer = gl.createFramebuffer(); +// if (!buffer) return false; +// this.framebufferMap.set(name, buffer); +// return true; +// } + +// /** +// * 删除一个帧缓冲对象 +// * @param name 帧缓冲名称 +// * @returns 是否删除成功 +// */ +// deleteFramebuffer(name: string): boolean { +// const gl = this.gl; +// if (!gl) return false; +// const buffer = this.framebufferMap.get(name); +// if (!buffer) return false; +// gl.deleteFramebuffer(buffer); +// return this.framebufferMap.delete(name); +// } + +// /** +// * 切换着色器程序 +// * @param program 着色器程序 +// */ +// useProgram(program: ShaderProgram) { +// if (!this.gl) return; +// if (program.element !== this) { +// logger.error(17); +// return; +// } +// if (this.program !== program) { +// this.program?.unload(); +// this.program = program; +// this.gl.useProgram(program.program); +// program.load(); +// } +// this.shaderRenderDirty = true; +// } + +// /** +// * 创建一个着色器程序 +// * @param vs 顶点着色器,可选 +// * @param fs 片元着色器,可选 +// */ +// createProgram(vs?: string, fs?: string) { +// const program = new ShaderProgram(this, vs, fs); +// this.programs.add(program); +// return program; +// } + +// /** +// * 删除一个着色器程序 +// * @param program 要删除的着色器程序 +// */ +// deleteProgram(program: ShaderProgram) { +// if (program.element !== this) { +// logger.error(18); +// return; +// } +// program.destroy(); +// this.programs.delete(program); +// } + +// destroy(): void { +// this.programs.forEach(v => v.destroy()); +// super.destroy(); +// } + +// // ----- 初始化部分 + +// private init() { +// const gl = this.gl; +// if (!gl) return; +// gl.enable(gl.DEPTH_TEST); +// gl.enable(gl.BLEND); +// gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); +// gl.depthFunc(gl.LEQUAL); +// } +// } + +export class ShaderProgram extends GL2Program { + protected readonly prefix: IGL2ProgramPrefix = SHADER_PREFIX; + + constructor(gl2: GL2, vs?: string, fs?: string) { + super(gl2, vs, fs); + if (!vs) this.vs(DEFAULT_VS); + if (!fs) this.fs(DEFAULT_FS); + if (!vs && !fs) { + this.modified = false; } - gl.framebufferTexture2D( - gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0, - gl.TEXTURE_2D, - tex, - 0 - ); - this.draw(gl, program, param, indices); - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - gl.bindTexture(gl.TEXTURE_2D, null); - } - - /** - * 创建一个帧缓冲对象 - * @param name 帧缓冲名称 - * @returns 是否创建成功 - */ - createFramebuffer(name: string): boolean { - const gl = this.gl; - if (!gl) return false; - const buffer = gl.createFramebuffer(); - if (!buffer) return false; - this.framebufferMap.set(name, buffer); - return true; - } - - /** - * 删除一个帧缓冲对象 - * @param name 帧缓冲名称 - * @returns 是否删除成功 - */ - deleteFramebuffer(name: string): boolean { - const gl = this.gl; - if (!gl) return false; - const buffer = this.framebufferMap.get(name); - if (!buffer) return false; - gl.deleteFramebuffer(buffer); - return this.framebufferMap.delete(name); - } - - /** - * 切换着色器程序 - * @param program 着色器程序 - */ - useProgram(program: ShaderProgram) { - if (!this.gl) return; - if (program.element !== this) { - logger.error(17); - return; - } - if (this.program !== program) { - this.program?.unload(); - this.program = program; - this.gl.useProgram(program.program); - program.load(); - } - this.shaderRenderDirty = true; - } - - /** - * 创建一个着色器程序 - * @param vs 顶点着色器,可选 - * @param fs 片元着色器,可选 - */ - createProgram(vs?: string, fs?: string) { - const program = new ShaderProgram(this, vs, fs); - this.programs.add(program); - return program; - } - - /** - * 删除一个着色器程序 - * @param program 要删除的着色器程序 - */ - deleteProgram(program: ShaderProgram) { - if (program.element !== this) { - logger.error(18); - return; - } - program.destroy(); - this.programs.delete(program); - } - - destroy(): void { - this.programs.forEach(v => v.destroy()); - super.destroy(); - } - - // ----- 初始化部分 - - private init() { - const gl = this.gl; - if (!gl) return; - gl.enable(gl.DEPTH_TEST); - gl.enable(gl.BLEND); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); - gl.depthFunc(gl.LEQUAL); - } -} - -type _U1 = [x0: number]; -type _U2 = [x0: number, x1: number]; -type _U3 = [x0: number, x1: number, x2: number]; -type _U4 = [x0: number, x1: number, x2: number, x3: number]; -type _UV = [data: T, srcOffset?: number, srcLength?: number]; -type _A = [data: T]; - -interface UniformSetFn { - [UniformType.Uniform1f]: _U1; - [UniformType.Uniform1fv]: _UV; - [UniformType.Uniform1i]: _U1; - [UniformType.Uniform1iv]: _UV; - [UniformType.Uniform1ui]: _U1; - [UniformType.Uniform1uiv]: _UV; - [UniformType.Uniform2f]: _U2; - [UniformType.Uniform2fv]: _UV; - [UniformType.Uniform2i]: _U2; - [UniformType.Uniform2iv]: _UV; - [UniformType.Uniform2ui]: _U2; - [UniformType.Uniform2uiv]: _UV; - [UniformType.Uniform3f]: _U3; - [UniformType.Uniform3fv]: _UV; - [UniformType.Uniform3i]: _U3; - [UniformType.Uniform3iv]: _UV; - [UniformType.Uniform3ui]: _U3; - [UniformType.Uniform3uiv]: _UV; - [UniformType.Uniform4f]: _U4; - [UniformType.Uniform4fv]: _UV; - [UniformType.Uniform4i]: _U4; - [UniformType.Uniform4iv]: _UV; - [UniformType.Uniform4ui]: _U4; - [UniformType.Uniform4uiv]: _UV; -} - -interface AttribSetFn { - [AttribType.Attrib1f]: _U1; - [AttribType.Attrib1fv]: _A; - [AttribType.Attrib2f]: _U2; - [AttribType.Attrib2fv]: _A; - [AttribType.Attrib3f]: _U3; - [AttribType.Attrib3fv]: _A; - [AttribType.Attrib4f]: _U4; - [AttribType.Attrib4fv]: _A; - [AttribType.AttribI4i]: _U4; - [AttribType.AttribI4iv]: _A; - [AttribType.AttribI4ui]: _U4; - [AttribType.AttribI4uiv]: _A; -} - -export interface IShaderUniform { - /** 这个 uniform 变量的内存位置 */ - readonly location: WebGLUniformLocation; - /** 这个 uniform 变量的类型 */ - readonly type: T; - /** 这个量所处的着色器程序 */ - readonly program: ShaderProgram; - /** - * 设置这个 uniform 变量的值, - * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL2RenderingContext/uniform - * @param params 要传递的参数,例如 uniform2f 就要传递 x0 x1 两个参数等,可以参考 mdn 文档 - */ - set(...params: UniformSetFn[T]): void; -} - -export interface IShaderAttrib { - /** 这个 attribute 常量的内存位置 */ - readonly location: number; - /** 这个 attribute 常量的类型 */ - readonly type: T; - /** 这个量所处的着色器程序 */ - readonly program: ShaderProgram; - /** - * 设置这个 attribute 常量的值, - * 浮点数参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/vertexAttrib - * 整数参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/vertexAttribI - * @param params 要传递的参数 - */ - set(...params: AttribSetFn[T]): void; -} - -export interface IShaderAttribArray { - /** 这个 attribute 常量的内存位置 */ - readonly location: number; - /** 这个 attribute 所用的缓冲区信息 */ - readonly data: WebGLBuffer; - /** 这个量所处的着色器程序 */ - readonly program: ShaderProgram; - /** - * 修改缓冲区数据,会更改数据大小,重新分配内存,不更改数据大小的情况下建议使用 {@link sub} 代替。 - * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/bufferData - * @param data 数据 - * @param usage 用途 - */ - buffer(data: AllowSharedBufferSource | null, usage: GLenum): void; - /** - * 修改缓冲区数据,会更改数据大小,重新分配内存,不更改数据大小的情况下建议使用 {@link sub} 代替。 - * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/bufferData - * @param data 数据 - * @param usage 用途 - * @param srcOffset 数据偏移量 - * @param length 数据长度 - */ - buffer( - data: ArrayBufferView, - usage: GLenum, - srcOffset: number, - length?: number - ): void; - /** - * 修改缓冲区数据,但是不修改数据大小,不重新分配内存。 - * 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferSubData - * @param dstByteOffset 数据修改的起始位置 - * @param srcData 数据 - */ - sub(dstByteOffset: GLintptr, srcData: AllowSharedBufferSource): void; - /** - * 修改缓冲区数据,但是不修改数据大小,不重新分配内存。 - * 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferSubData - * @param dstByteOffset 数据修改的起始位置 - * @param srcData 数据 - * @param srcOffset 数据偏移量 - * @param length 数据长度 - */ - sub( - dstByteOffset: GLintptr, - srcData: ArrayBufferView, - srcOffset: number, - length?: GLuint - ): void; - /** - * 告诉 gpu 将读取此 attribute 数据 - * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/vertexAttribPointer - * @param size 单个数据大小 - * @param type 数据类型 - * @param normalized 是否要经过归一化处理 - * @param stride 每一部分字节偏移量 - * @param offset 第一部分字节偏移量 - */ - pointer( - size: GLint, - type: GLenum, - normalized: GLboolean, - stride: GLsizei, - offset: GLintptr - ): void; - /** - * 告诉 gpu 将由整数类型读取此 attribute 数据 - * 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/vertexAttribIPointer - * @param size 单个数据大小 - * @param type 数据类型 - * @param stride 每一部分字节偏移量 - * @param offset 第一部分字节偏移量 - */ - pointerI( - size: GLint, - type: GLenum, - stride: GLsizei, - offset: GLintptr - ): void; - /** - * 设置顶点指针更新时刻。 - * 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/vertexAttribDivisor - * @param divisor 每多少个实例更新一次,0表示每个顶点都更新 - */ - divisor(divisor: number): void; - /** - * 启用这个顶点数据 - */ - enable(): void; - /** - * 禁用这个顶点数据 - */ - disable(): void; -} - -export interface IShaderIndices { - /** 这个顶点索引所用的缓冲区信息 */ - readonly data: WebGLBuffer; - /** 这个量所处的着色器程序 */ - readonly program: ShaderProgram; - /** - * 修改缓冲区数据,会更改数据大小,重新分配内存,不更改数据大小的情况下建议使用 {@link sub} 代替。 - * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/bufferData - * @param data 数据 - * @param usage 用途 - */ - buffer(data: AllowSharedBufferSource | null, usage: GLenum): void; - /** - * 修改缓冲区数据,会更改数据大小,重新分配内存,不更改数据大小的情况下建议使用 {@link sub} 代替。 - * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/bufferData - * @param data 数据 - * @param usage 用途 - * @param srcOffset 数据偏移量 - * @param length 数据长度 - */ - buffer( - data: ArrayBufferView, - usage: GLenum, - srcOffset: number, - length?: number - ): void; - /** - * 修改缓冲区数据,但是不修改数据大小,不重新分配内存。 - * 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferSubData - * @param dstByteOffset 数据修改的起始位置 - * @param srcData 数据 - */ - sub(dstByteOffset: GLintptr, srcData: AllowSharedBufferSource): void; - /** - * 修改缓冲区数据,但是不修改数据大小,不重新分配内存。 - * 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferSubData - * @param dstByteOffset 数据修改的起始位置 - * @param srcData 数据 - * @param srcOffset 数据偏移量 - * @param length 数据长度 - */ - sub( - dstByteOffset: GLintptr, - srcData: ArrayBufferView, - srcOffset: number, - length?: GLuint - ): void; -} - -export interface IShaderUniformMatrix { - /** 矩阵的内存位置 */ - readonly location: WebGLUniformLocation; - /** 矩阵类型 */ - readonly type: UniformMatrix; - /** 这个量所处的着色器程序 */ - readonly program: ShaderProgram; - /** - * 设置矩阵的值,参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL2RenderingContext/uniformMatrix - * @param transpose 是否转置矩阵 - * @param data 矩阵数据,列主序 - * @param srcOffset 数据偏移量 - * @param srcLength 数据长度 - */ - set( - transpose: GLboolean, - data: Float32List, - srcOffset?: number, - srcLength?: number - ): void; -} - -export interface IShaderUniformBlock { - /** 这个 uniform block 的内存地址 */ - readonly location: GLuint; - /** 与这个 uniform block 所绑定的缓冲区 */ - readonly buffer: WebGLBuffer; - /** 这个 uniform block 的大小 */ - readonly size: number; - /** 这个量所处的着色器程序 */ - readonly program: ShaderProgram; - /** - * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL2RenderingContext/bindBufferBase - * @param srcData 要设置为的值 - */ - set(srcData: AllowSharedBufferSource | null): void; - /** - * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL2RenderingContext/bindBufferBase - * @param srcData 要设置为的值 - * @param srcOffset 数据偏移量 - * @param length 数据长度 - */ - set(srcData: ArrayBufferView, srcOffset: number, length?: number): void; -} - -export interface IShaderTexture2D { - /** 纹理对象 */ - readonly texture: WebGLTexture; - /** 宽度 */ - readonly width: number; - /** 高度 */ - readonly height: number; - /** 纹理所属索引 */ - readonly index: number; - /** 这个量所处的着色器程序 */ - readonly program: ShaderProgram; - /** - * 设置这个纹理的图像,不建议使用,会修改宽高 - * @param source 要设置成的图像源 - */ - set(source: TexImageSource): void; - /** - * 设置纹理的一部分信息,不会修改宽高 - * @param source 要设置的图像源 - * @param x 要设置到的起始点横坐标 - * @param y 要设置到的起始点纵坐标 - * @param width 宽度 - * @param height 高度 - */ - sub( - source: TexImageSource, - x: number, - y: number, - width: number, - height: number - ): void; -} - -class ShaderUniform implements IShaderUniform { - constructor( - readonly type: T, - readonly location: WebGLUniformLocation, - readonly gl: WebGL2RenderingContext, - readonly program: ShaderProgram - ) {} - - set(...params: UniformSetFn[T]): void { - this.gl.vertexAttribIPointer; - // 因为ts类型推导的限制,类型肯定正确,但是推导不出,所以这里直接 as any 屏蔽掉类型推导 - const x0 = params[0] as any; - const x1 = params[1] as any; - const x2 = params[2] as any; - const x3 = params[3] as any; - switch (this.type) { - case UniformType.Uniform1f: - this.gl.uniform1f(this.location, x0); - break; - case UniformType.Uniform1fv: - this.gl.uniform1fv(this.location, x0, x1, x2); - break; - case UniformType.Uniform1i: - this.gl.uniform1i(this.location, x0); - break; - case UniformType.Uniform1iv: - this.gl.uniform1iv(this.location, x0, x1, x2); - break; - case UniformType.Uniform1ui: - this.gl.uniform1ui(this.location, x0); - break; - case UniformType.Uniform1uiv: - this.gl.uniform1uiv(this.location, x0, x1, x2); - break; - case UniformType.Uniform2f: - this.gl.uniform2f(this.location, x0, x1); - break; - case UniformType.Uniform2fv: - this.gl.uniform2fv(this.location, x0, x1, x2); - break; - case UniformType.Uniform2i: - this.gl.uniform2i(this.location, x0, x1); - break; - case UniformType.Uniform2iv: - this.gl.uniform2iv(this.location, x0, x1, x2); - break; - case UniformType.Uniform2ui: - this.gl.uniform2ui(this.location, x0, x1); - break; - case UniformType.Uniform2uiv: - this.gl.uniform2uiv(this.location, x0, x1, x2); - break; - case UniformType.Uniform3f: - this.gl.uniform3f(this.location, x0, x1, x2); - break; - case UniformType.Uniform3fv: - this.gl.uniform3fv(this.location, x0, x1, x2); - break; - case UniformType.Uniform3i: - this.gl.uniform3i(this.location, x0, x1, x2); - break; - case UniformType.Uniform3iv: - this.gl.uniform3iv(this.location, x0, x1, x2); - break; - case UniformType.Uniform3ui: - this.gl.uniform3ui(this.location, x0, x1, x2); - break; - case UniformType.Uniform3uiv: - this.gl.uniform3uiv(this.location, x0, x1, x2); - break; - case UniformType.Uniform4f: - this.gl.uniform4f(this.location, x0, x1, x2, x3); - break; - case UniformType.Uniform4fv: - this.gl.uniform4fv(this.location, x0, x1, x2); - break; - case UniformType.Uniform4i: - this.gl.uniform4i(this.location, x0, x1, x2, x3); - break; - case UniformType.Uniform4iv: - this.gl.uniform4iv(this.location, x0, x1, x2); - break; - case UniformType.Uniform4ui: - this.gl.uniform4ui(this.location, x0, x1, x2, x3); - break; - case UniformType.Uniform4uiv: - this.gl.uniform4uiv(this.location, x0, x1, x2); - break; - } - } -} - -class ShaderAttrib implements IShaderAttrib { - constructor( - readonly type: T, - readonly location: number, - readonly gl: WebGL2RenderingContext, - readonly program: ShaderProgram - ) {} - - set(...params: AttribSetFn[T]) { - // 因为ts类型推导的限制,类型肯定正确,但是推导不出,所以这里直接 as any 屏蔽掉类型推导 - const x0 = params[0] as any; - const x1 = params[1] as any; - const x2 = params[2] as any; - const x3 = params[3] as any; - switch (this.type) { - case AttribType.Attrib1f: - this.gl.vertexAttrib1f(this.location, x0); - break; - case AttribType.Attrib1fv: - this.gl.vertexAttrib1fv(this.location, x0); - break; - case AttribType.Attrib2f: - this.gl.vertexAttrib2f(this.location, x0, x1); - break; - case AttribType.Attrib2fv: - this.gl.vertexAttrib2fv(this.location, x0); - break; - case AttribType.Attrib3f: - this.gl.vertexAttrib3f(this.location, x0, x1, x2); - break; - case AttribType.Attrib3fv: - this.gl.vertexAttrib3fv(this.location, x0); - break; - case AttribType.Attrib4f: - this.gl.vertexAttrib4f(this.location, x0, x1, x2, x3); - break; - case AttribType.Attrib4fv: - this.gl.vertexAttrib4fv(this.location, x0); - break; - case AttribType.AttribI4i: - this.gl.vertexAttribI4i(this.location, x0, x1, x2, x3); - break; - case AttribType.AttribI4iv: - this.gl.vertexAttribI4iv(this.location, x0); - break; - case AttribType.AttribI4ui: - this.gl.vertexAttribI4ui(this.location, x0, x1, x2, x3); - break; - case AttribType.AttribI4uiv: - this.gl.vertexAttribI4uiv(this.location, x0); - break; - default: { - logger.warn(26); - return; - } - } - } -} - -class ShaderAttribArray implements IShaderAttribArray { - constructor( - readonly data: WebGLBuffer, - readonly location: number, - readonly gl: WebGL2RenderingContext, - readonly program: ShaderProgram - ) {} - - buffer(data: AllowSharedBufferSource | null, usage: GLenum): void; - buffer( - data: ArrayBufferView, - usage: GLenum, - srcOffset: number, - length?: number - ): void; - buffer(data: any, usage: any, srcOffset?: any, length?: any): void { - const gl = this.gl; - gl.bindBuffer(gl.ARRAY_BUFFER, this.data); - if (typeof srcOffset === 'number') { - gl.bufferData(gl.ARRAY_BUFFER, data, usage, srcOffset, length); - } else { - gl.bufferData(gl.ARRAY_BUFFER, data, usage); - } - } - - sub(dstByteOffset: GLintptr, srcData: AllowSharedBufferSource): void; - sub( - dstByteOffset: GLintptr, - srcData: ArrayBufferView, - srcOffset: number, - length?: GLuint - ): void; - sub(dstOffset: any, data: any, offset?: any, length?: any): void { - const gl = this.gl; - gl.bindBuffer(gl.ARRAY_BUFFER, this.data); - if (typeof offset === 'number') { - gl.bufferSubData(gl.ARRAY_BUFFER, dstOffset, data, offset, length); - } else { - gl.bufferSubData(gl.ARRAY_BUFFER, dstOffset, data); - } - } - - pointer( - p0: GLint, - p1: GLenum, - p2: GLboolean, - p3: GLsizei, - p4: GLintptr - ): void { - const gl = this.gl; - gl.bindBuffer(gl.ARRAY_BUFFER, this.data); - gl.vertexAttribPointer(this.location, p0, p1, p2, p3, p4); - } - - pointerI( - size: GLint, - type: GLenum, - stride: GLsizei, - offset: GLintptr - ): void { - const gl = this.gl; - gl.bindBuffer(gl.ARRAY_BUFFER, this.data); - gl.vertexAttribIPointer(this.location, size, type, stride, offset); - } - - divisor(divisor: number): void { - const gl = this.gl; - gl.vertexAttribDivisor(this.location, divisor); - } - - enable(): void { - this.gl.enableVertexAttribArray(this.location); - } - - disable(): void { - this.gl.disableVertexAttribArray(this.location); - } -} - -class ShaderIndices implements IShaderIndices { - constructor( - readonly data: WebGLBuffer, - readonly gl: WebGL2RenderingContext, - readonly program: ShaderProgram - ) {} - - buffer(data: AllowSharedBufferSource | null, usage: GLenum): void; - buffer( - data: ArrayBufferView, - usage: GLenum, - srcOffset: number, - length?: number - ): void; - buffer(p0: any, p1: any, p2?: any, p3?: any): void { - const gl = this.gl; - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.data); - if (typeof p2 === 'number') { - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, p0, p1, p2, p3); - } else { - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, p0, p1); - } - } - - sub(dstByteOffset: GLintptr, srcData: AllowSharedBufferSource): void; - sub( - dstByteOffset: GLintptr, - srcData: ArrayBufferView, - srcOffset: number, - length?: GLuint - ): void; - sub(p0: any, p1: any, p2?: any, p3?: any): void { - const gl = this.gl; - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.data); - if (typeof p2 === 'number') { - gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, p0, p1, p2, p3); - } else { - gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, p0, p1); - } - } -} - -class ShaderUniformMatrix implements IShaderUniformMatrix { - constructor( - readonly type: UniformMatrix, - readonly location: WebGLUniformLocation, - readonly gl: WebGL2RenderingContext, - readonly program: ShaderProgram - ) {} - - set(x2: GLboolean, x3: Float32List, x4?: number, x5?: number): void { - switch (this.type) { - case UniformMatrix.UMatrix2x2: - this.gl.uniformMatrix2fv(this.location, x2, x3, x4, x5); - break; - case UniformMatrix.UMatrix2x3: - this.gl.uniformMatrix2x3fv(this.location, x2, x3, x4, x5); - break; - case UniformMatrix.UMatrix2x4: - this.gl.uniformMatrix2x4fv(this.location, x2, x3, x4, x5); - break; - case UniformMatrix.UMatrix3x2: - this.gl.uniformMatrix3x2fv(this.location, x2, x3, x4, x5); - break; - case UniformMatrix.UMatrix3x3: - this.gl.uniformMatrix3fv(this.location, x2, x3, x4, x5); - break; - case UniformMatrix.UMatrix3x4: - this.gl.uniformMatrix3x4fv(this.location, x2, x3, x4, x5); - break; - case UniformMatrix.UMatrix4x2: - this.gl.uniformMatrix4x2fv(this.location, x2, x3, x4, x5); - break; - case UniformMatrix.UMatrix4x3: - this.gl.uniformMatrix4x3fv(this.location, x2, x3, x4, x5); - break; - case UniformMatrix.UMatrix4x4: - this.gl.uniformMatrix4fv(this.location, x2, x3, x4, x5); - break; - } - } -} - -class ShaderUniformBlock implements IShaderUniformBlock { - constructor( - readonly location: number, - readonly size: number, - readonly buffer: WebGLBuffer, - readonly binding: number, - readonly gl: WebGL2RenderingContext, - readonly program: ShaderProgram - ) {} - - set(srcData: AllowSharedBufferSource | null): void; - set(srcData: ArrayBufferView, srcOffset: number, length?: number): void; - set(srcData: unknown, srcOffset?: unknown, length?: unknown): void { - const gl = this.gl; - const buffer = this.buffer; - gl.bindBuffer(gl.UNIFORM_BUFFER, buffer); - if (srcOffset !== void 0) { - // @ts-ignore - gl.bufferSubData(gl.UNIFORM_BUFFER, 0, srcData, srcOffset, length); - } else { - // @ts-ignore - gl.bufferSubData(gl.UNIFORM_BUFFER, 0, srcData); - } - gl.bindBufferBase(gl.UNIFORM_BUFFER, this.binding, buffer); - } -} - -class ShaderTexture2D implements IShaderTexture2D { - constructor( - readonly texture: WebGLTexture, - readonly index: number, - readonly uniform: IShaderUniform, - readonly gl: WebGL2RenderingContext, - readonly program: ShaderProgram, - public width: number = 0, - public height: number = 0 - ) { - uniform.set(index); - } - - set(source: TexImageSource): void { - const gl = this.gl; - gl.activeTexture(gl.TEXTURE0 + this.index); - gl.bindTexture(gl.TEXTURE_2D, this.texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texImage2D( - gl.TEXTURE_2D, - 0, - gl.RGBA, - gl.RGBA, - gl.UNSIGNED_BYTE, - source - ); - if (source instanceof VideoFrame) { - this.width = source.codedWidth; - this.height = source.codedHeight; - } else { - this.width = source.width; - this.height = source.height; - } - } - - sub( - source: TexImageSource, - x: number, - y: number, - width: number, - height: number - ): void { - const gl = this.gl; - gl.activeTexture(gl.TEXTURE0 + this.index); - gl.bindTexture(gl.TEXTURE_2D, this.texture); - - // 进行边界检查,避免超出纹理边界 - if (x + width > this.width || y + height > this.height) { - logger.warn(32); - width = Math.min(width, this.width - x); - height = Math.min(height, this.height - y); - } - - gl.texSubImage2D( - gl.TEXTURE_2D, - 0, - x, - y, - width, - height, - gl.RGBA, - gl.UNSIGNED_BYTE, - source - ); - } -} - -interface DrawArraysParam { - mode: GLenum; - first: number; - count: number; -} - -interface DrawElementsParam { - mode: GLenum; - count: number; - type: GLenum; - offset: GLintptr; -} - -interface DrawArraysInstancedParam { - mode: GLenum; - first: number; - count: number; - instanceCount: number; -} - -interface DrawElementsInstancedParam { - mode: GLenum; - count: number; - type: GLenum; - offset: GLintptr; - instanceCount: number; -} - -export interface DrawParamsMap { - [RenderMode.Arrays]: DrawArraysParam; - [RenderMode.ArraysInstanced]: DrawArraysInstancedParam; - [RenderMode.Elements]: DrawElementsParam; - [RenderMode.ElementsInstanced]: DrawElementsInstancedParam; -} - -interface ShaderProgramEvent { - load: []; - unload: []; -} - -export class ShaderProgram extends EventEmitter { - /** 顶点着色器 */ - private vertex: string = DEFAULT_VS; - /** 片元着色器 */ - private fragment: string = DEFAULT_FS; - /** glsl版本 */ - version: ShaderVersion; - /** webgl2上下文 */ - gl: WebGL2RenderingContext; - /** 当前着色器程序的着色器渲染元素 */ - element: Shader; - - /** uniform存放地址 */ - private uniform: Map> = new Map(); - /** attribute存放地址,300版本里面叫做in */ - private attribute: Map> = new Map(); - /** attribute array 存放地址 */ - private attribArray: Map = new Map(); - /** 顶点索引存放地址 */ - private indices: Map = new Map(); - /** uniform矩阵存放地址 */ - private matrix: Map = new Map(); - /** uniform block 存放地址 */ - private block: Map = new Map(); - /** 纹理存放地址 */ - private texture: Map = new Map(); - /** 当前编译完成的shader程序 */ - private shader: CompiledShader | null = null; - /** 当前的webgl程序 */ - program: WebGLProgram | null = null; - /** 准备函数 */ - private readyFn?: () => boolean; - /** 当前正在使用的顶点索引数组 */ - usingIndices: IShaderIndices | null = null; - - /** 着色器内容是否是默认内容,可以用于优化空着色器 */ - modified: boolean = false; - /** 是否使用默认内容 */ - defaultReady: boolean = true; - /** 渲染模式 */ - renderMode: RenderMode = RenderMode.Elements; - - private arraysParams: DrawArraysParam | null = null; - private elementsParams: DrawElementsParam | null = null; - private arraysInstancedParams: DrawArraysInstancedParam | null = null; - private elementsInstancedParams: DrawElementsInstancedParam | null = null; - - /** 是否需要重新编译着色器 */ - private shaderDirty: boolean = true; - - constructor(shader: Shader, vs?: string, fs?: string) { - super(); - if (vs) this.vs(vs); - if (fs) this.fs(fs); - this.element = shader; - this.gl = shader.gl; - this.version = shader.VERSION_ES_100; - if (vs || fs) this.requestCompile(); - } - - /** - * 使用这个着色器程序时,在渲染之前执行的准备函数 - * @param fn 准备函数,返回 false 时将不执行绘制 - */ - setReady(fn: () => boolean) { - this.readyFn = fn; - } - - /** - * 执行准备函数 - */ - ready(): boolean { - return this.readyFn?.() ?? true; - } - - /** - * 设置是否使用默认内容,设为 false 后,将不再将子元素的内容绑定到纹理上 - */ - useDefault(use: boolean) { - this.defaultReady = use; - } - - /** - * 设置渲染模式,目前可选 {@link Shader.DRAW_ARRAYS} 至 {@link Shader.DRAW_INSTANCED} - */ - mode(mode: RenderMode) { - this.renderMode = mode; - } - - /** - * 获取指定渲染模式的渲染参数 - * @param param 渲染模式 - */ - getDrawParams(param: T): DrawParamsMap[T] | null { - switch (param) { - case RenderMode.Arrays: - return this.arraysParams as DrawParamsMap[T]; - case RenderMode.ArraysInstanced: - return this.arraysInstancedParams as DrawParamsMap[T]; - case RenderMode.Elements: - return this.elementsParams as DrawParamsMap[T]; - case RenderMode.ElementsInstanced: - return this.elementsInstancedParams as DrawParamsMap[T]; - } - return null; - } - - /** - * 设置 DRAW_ARRAYS 模式下的渲染参数 - * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/drawArrays - * @param mode 渲染模式 - * @param first 第一个元素的位置 - * @param count 渲染多少个元素 - */ - paramArrays(mode: GLenum, first: number, count: number) { - if (!this.arraysParams) { - this.arraysParams = { mode, first, count }; - } else { - this.arraysParams.mode = mode; - this.arraysParams.first = first; - this.arraysParams.count = count; - } - } - - /** - * 设置 DRAW_ARRAYS_INSTANCED 模式下的渲染参数 - * 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/drawArraysInstanced - * @param mode 渲染模式 - * @param first 第一个元素的位置 - * @param count 渲染多少个元素 - * @param instanceCount 渲染实例数量 - */ - paramArraysInstanced( - mode: GLenum, - first: number, - count: number, - instanceCount: number - ) { - if (!this.arraysInstancedParams) { - this.arraysInstancedParams = { mode, first, count, instanceCount }; - } else { - this.arraysInstancedParams.mode = mode; - this.arraysInstancedParams.first = first; - this.arraysInstancedParams.count = count; - this.arraysInstancedParams.instanceCount = instanceCount; - } - } - - /** - * 设置 DRAW_ELEMENTS 模式下的渲染参数 - * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/drawElements - * @param mode 渲染模式 - * @param count 渲染元素数量 - * @param type 数据类型 - * @param offset 偏移量 - */ - paramElements(mode: GLenum, count: number, type: GLenum, offset: number) { - if (!this.elementsParams) { - this.elementsParams = { mode, count, type, offset }; - } else { - this.elementsParams.mode = mode; - this.elementsParams.count = count; - this.elementsParams.type = type; - this.elementsParams.offset = offset; - } - } - - /** - * 设置 DRAW_ELEMENTS 模式下的渲染参数 - * 参考 https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/drawElementsInstanced - * @param mode 渲染模式 - * @param count 渲染元素数量 - * @param type 数据类型 - * @param offset 偏移量 - * @param instanceCount 渲染实例数量 - */ - paramElementsInstanced( - mode: GLenum, - count: number, - type: GLenum, - offset: number, - instanceCount: number - ) { - if (!this.elementsInstancedParams) { - this.elementsInstancedParams = { - mode, - count, - type, - offset, - instanceCount - }; - } else { - this.elementsInstancedParams.mode = mode; - this.elementsInstancedParams.count = count; - this.elementsInstancedParams.type = type; - this.elementsInstancedParams.offset = offset; - this.elementsInstancedParams.instanceCount = instanceCount; - } - } - - /** - * 切换渲染时使用的顶点索引 - * @param name 要使用的顶点索引名称 - */ - useIndices(name: string): void; - useIndices(name: IShaderIndices): void; - useIndices(name: string | IShaderIndices) { - if (typeof name === 'string') { - const indices = this.getIndices(name); - if (!indices) { - logger.warn(30, name); - return; - } - this.usingIndices = indices; - } else { - if ([...this.indices.values()].includes(name)) { - this.usingIndices = name; + this.setReady(() => { + const tex = this.getTexture('u_sampler'); + if (!tex) return false; + const c = this.element.canvas; + if (tex.width === c.width && tex.height === c.height) { + tex.sub(c, 0, 0, c.width, c.height); } else { - logger.warn(31); + tex.set(c); } - } - } - - /** - * 设置着色器使用的glsl版本,默认使用100版本,注意切换后一定要重新设置着色器内容 - * @param version glsl版本,可选 {@link Shader.VERSION_ES_100} 或 {@link Shader.VERSION_ES_300} - */ - setVersion(version: ShaderVersion) { - this.version = version; - } - - /** - * 检查当前是否需要重新编译着色器,如果需要,则重新编译 - * @param force 是否强制重新编译 - */ - requestCompile(force: boolean = false): boolean { - if (!force && !this.shaderDirty) return false; - return this.compile(); - } - - /** - * 设置顶点着色器内容 - * @param vs 顶点着色器 - */ - vs(vs: string) { - this.vertex = vs; - this.shaderDirty = true; - this.modified = true; - } - - /** - * 设置片元着色器内容 - * @param fs 片元着色器 - */ - fs(fs: string) { - this.fragment = fs; - this.shaderDirty = true; - this.modified = true; - } - - /** - * 当这个程序被卸载时执行的函数 - */ - unload() { - this.attribArray.forEach(v => { - v.disable(); + return true; }); - this.emit('load'); } - /** - * 当这个程序被加载(使用)时执行的函数 - */ - load() { - this.attribArray.forEach(v => { - v.enable(); - }); - this.emit('unload'); - } - - /** - * 获取一个uniform,需要事先定义,否则返回null - * @param uniform uniform名称 - */ - getUniform( - uniform: string - ): IShaderUniform | null { - return (this.uniform.get(uniform) as IShaderUniform) ?? null; - } - - /** - * 获取一个attribute,需要事先定义,否则返回null - * @param attrib attribute名称 - */ - getAttribute( - attrib: string - ): IShaderAttrib | null { - return (this.attribute.get(attrib) as IShaderAttrib) ?? null; - } - - /** - * 获取一个attribute array,需要事先定义,否则返回null - * @param name attribute array名称 - */ - getAttribArray(name: string): IShaderAttribArray | null { - return this.attribArray.get(name) ?? null; - } - - /** - * 获取一个顶点索引数组,需要提前定义,否则返回null - * @param name 顶点索引数组的名称 - */ - getIndices(name: string): IShaderIndices | null { - return this.indices.get(name) ?? null; - } - - /** - * 获取一个 uniform matrix,需要事先定义,否则返回null - * @param matrix uniform matrix 的名称 - */ - getMatrix(matrix: string): IShaderUniformMatrix | null { - return this.matrix.get(matrix) ?? null; - } - - /** - * 获取一个 uniform block,例如 UBO,需要事先定义,否则返回null - * @param block uniform block 的名称 - */ - getUniformBlock(block: string): IShaderUniformBlock | null { - return this.block.get(block) ?? null; - } - - /** - * 获取一个 texture,需要事先定义,否则返回null - * @param name texture 的名称 - */ - getTexture(name: string): IShaderTexture2D | null { - return this.texture.get(name) ?? null; - } - - /** - * 定义一个 uniform 变量,并存入本着色器程序的 uniform 变量映射 - * @param uniform uniform 变量名 - * @param type uniform 类型,可选 {@link Shader.UNIFORM_1f} 至 {@link Shader.UNIFORM_4uiv} - * @returns uniform 变量的操作对象,可用于设置其值 - */ - defineUniform( - uniform: string, - type: T - ): IShaderUniform | null { - const u = this.getUniform(uniform); - if (u) { - if (u.type === type) return u; - else { - logger.warn(28, 'uniform', uniform); - return null; - } - } - const program = this.program; - const gl = this.element.gl; - if (!program || !gl) return null; - const location = gl.getUniformLocation(program, uniform); - if (!location) return null; - const obj = new ShaderUniform(type, location, gl, this); - this.uniform.set(uniform, obj); - return obj; - } - - /** - * 定义一个 uniform 矩阵变量,并存入本着色器程序的 uniform 矩阵变量映射 - * @param uniform uniform 矩阵变量名 - * @param type uniform 矩阵类型,可选 {@link Shader.U_MATRIX_2x2} 至 {@link Shader.U_MATRIX_4x4} - * @returns uniform 矩阵变量的操作对象,可用于设置其值 - */ - defineUniformMatrix( - uniform: string, - type: UniformMatrix - ): IShaderUniformMatrix | null { - const u = this.getMatrix(uniform); - if (u) { - if (u.type === type) return u; - else { - logger.warn(28, 'uniform matrix', uniform); - return null; - } - } - const program = this.program; - const gl = this.element.gl; - if (!program || !gl) return null; - const location = gl.getUniformLocation(program, uniform); - if (!location) return null; - const obj = new ShaderUniformMatrix(type, location, gl, this); - this.matrix.set(uniform, obj); - return obj; - } - - /** - * 定义一个 attribute 常量,并存入本着色器程序的 attribute 常量映射,在 es 300 版本中叫做 in - * @param attrib attribute 常量名 - * @param type attribute 类型,可选 {@link Shader.Attrib1f} 至 {@link Shader.AttribI4uiv} - * @returns attribute 常量的操作对象,可用于设置其值 - */ - defineAttribute( - attrib: string, - type: T - ): IShaderAttrib | null { - const u = this.getAttribute(attrib); - if (u) { - if (u.type === type) return u; - else { - logger.warn(28, 'attribute', attrib); - return null; - } - } - const program = this.program; - const gl = this.element.gl; - if (!program || !gl) return null; - const location = gl.getAttribLocation(program, attrib); - if (location === -1) return null; - const obj = new ShaderAttrib(type, location, gl, this); - this.attribute.set(attrib, obj); - return obj; - } - - /** - * 定义一个顶点数组 - * @param name 顶点数组名称 - */ - defineAttribArray(name: string) { - const u = this.getAttribArray(name); - if (u) return u; - const program = this.program; - const gl = this.element.gl; - if (!program || !gl) return null; - const buffer = gl.createBuffer(); - if (!buffer) return null; - const location = gl.getAttribLocation(program, name); - if (location === -1) return null; - const obj = new ShaderAttribArray(buffer, location, gl, this); - this.attribArray.set(name, obj); - return obj; - } - - /** - * 定义一个顶点索引数组 - * @param name 顶点索引数组的名称 - */ - defineIndices(name: string) { - const u = this.getIndices(name); - if (u) return u; - const program = this.program; - const gl = this.element.gl; - if (!program || !gl) return null; - const buffer = gl.createBuffer(); - if (!buffer) return null; - const obj = new ShaderIndices(buffer, gl, this); - this.indices.set(name, obj); - return obj; - } - - /** - * 定义一个 uniform block,例如 UBO,并存入本着色器程序的 uniform block 映射 - * 用于一次性向着色器传输大量数据 - * @param block uniform block 名称 - * @param size 数据量,即数据长度,例如一个vec4就是4个长度 - * @param usage 缓冲区用途,例如 gl.STATIC_DRAW 是指会频繁读取但不会频繁写入 - * 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/bufferData - * 的 `usage` 参数 - * @param binding uniform block 的索引,例如这是你设置的第一个uniform block,就可以填0,第二个填1,以此类推 - * @returns uniform block 的操作对象,可用于设置其值 - */ - defineUniformBlock( - block: string, - size: number, - usage: number, - binding: number - ): IShaderUniformBlock | null { - if (this.version === this.element.VERSION_ES_100) { - logger.warn(24); - return null; - } - const u = this.getUniformBlock(block); - if (u) { - if (u.size === size) return u; - else { - logger.warn(28, 'uniform block', block); - return null; - } - } - const program = this.program; - const gl = this.element.gl; - if (!program || !gl) return null; - const loc = gl.getUniformBlockIndex(program, block); - if (loc === -1) return null; - const buf = gl.createBuffer(); - if (!buf) return null; - const data = new Float32Array(size); - data.fill(0); - gl.bindBuffer(gl.UNIFORM_BUFFER, buf); - gl.bufferData(gl.UNIFORM_BUFFER, data, usage); - gl.uniformBlockBinding(program, loc, binding); - gl.bindBufferBase(gl.UNIFORM_BUFFER, binding, buf); - const obj = new ShaderUniformBlock(loc, size, buf, binding, gl, this); - this.block.set(block, obj); - return obj; - } - - /** - * 定义一个材质 - * @param name 纹理名称 - * @param index 纹理索引,根据不同浏览器,其最大数量不一定相等,根据标准其数量应该大于等于 8 个, - * 因此考虑到兼容性,不建议纹理数量超过 8 个。 - * @param w 纹理的宽度 - * @param h 纹理的高度 - * @returns 这个 texture 的操作对象,可以用于设置其内容 - */ - defineTexture( - name: string, - index: number, - w?: number, - h?: number - ): IShaderTexture2D | null { - const u = this.getTexture(name); - if (u) { - if (u.index === index) return u; - else { - logger.warn(28, 'texture', name); - return null; - } - } - if (index > this.element.MAX_TEXTURE_COUNT) { - logger.warn(29); - return null; - } - const uni = this.defineUniform(name, UniformType.Uniform1i); - if (!uni) return null; - const program = this.program; - const gl = this.element.gl; - if (!program || !gl) return null; - const tex = gl.createTexture(); - if (!tex) return null; - const obj = new ShaderTexture2D(tex, index, uni, gl, this, w, h); - this.texture.set(name, obj); - return obj; - } - - /** - * 摧毁这个着色器程序,不要直接调用,请使用 {@link Shader.deleteProgram} 来删除一个着色器程序 - */ - destroy() { - this.clearProgram(); - } - - private clearProgram() { - if (!this.gl) return; - this.uniform.clear(); - this.attribute.clear(); - this.matrix.clear(); - this.gl.deleteProgram(this.program); - if (this.shader) { - this.gl.deleteShader(this.shader.vertex); - this.gl.deleteShader(this.shader.fragment); - } - this.block.forEach(v => { - this.gl.deleteBuffer(v.buffer); - }); - this.attribArray.forEach(v => { - this.gl.deleteBuffer(v.data); - }); - this.texture.forEach(v => { - this.gl.deleteTexture(v.texture); - }); - this.indices.forEach(v => { - this.gl.deleteBuffer(v.data); - }); - this.texture.clear(); - this.indices.clear(); - this.attribArray.clear(); - this.block.clear(); - } - - private compile() { - this.shaderDirty = false; - this.clearProgram(); - + protected override compile() { + const success = super.compile(); + if (!success) return false; const shader = this.element; const gl = shader.gl; if (!gl) return false; - const program = gl.createProgram(); - if (!program) return false; - - const vsPrefix = - this.version === shader.VERSION_ES_100 - ? SHADER_VERTEX_PREFIX_100 - : SHADER_VERTEX_PREFIX_300; - const fsPrefix = - this.version === shader.VERSION_ES_100 - ? SHADER_FRAGMENT_PREFIX_100 - : SHADER_FRAGMENT_PREFIX_300; - - const vertexShader = this.compileShader( - gl.VERTEX_SHADER, - vsPrefix + this.vertex - ); - const fragmentShader = this.compileShader( - gl.FRAGMENT_SHADER, - fsPrefix + this.fragment - ); - - if (!vertexShader || !fragmentShader) return false; - - gl.attachShader(program, vertexShader); - gl.attachShader(program, fragmentShader); - gl.linkProgram(program); - gl.useProgram(program); - - this.program = program; - this.shader = { vertex: vertexShader, fragment: fragmentShader }; const tex = this.defineAttribArray('a_texCoord'); const position = this.defineAttribArray('a_position'); const sampler = this.defineTexture('u_sampler', 0); @@ -1903,22 +516,4 @@ export class ShaderProgram extends EventEmitter { return true; } - - private compileShader(type: number, source: string): WebGLShader | null { - const gl = this.element.gl; - const shader = gl.createShader(type); - if (!shader) return null; - gl.shaderSource(shader, source); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - logger.error( - 13, - type === gl.VERTEX_SHADER ? 'vertex' : 'fragment', - gl.getShaderInfoLog(shader) ?? '' - ); - } - - return shader; - } } diff --git a/src/module/weather/rain.ts b/src/module/weather/rain.ts index 9565524..44df2cf 100644 --- a/src/module/weather/rain.ts +++ b/src/module/weather/rain.ts @@ -1,12 +1,10 @@ -import { - IShaderUniform, - Shader, - ShaderProgram, - UniformType -} from '@/core/render/shader'; +import { Shader, ShaderProgram } from '@/core/render/shader'; import { IWeather, WeatherController } from './weather'; import { MotaRenderer } from '@/core/render/render'; import { Container } from '@/core/render/container'; +import { GL2Program, IShaderUniform, UniformType } from '@/core/render/gl2'; +import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d'; +import { Transform } from '@/core/render/transform'; const rainVs = /* glsl */ ` in vec2 a_rainVertex; @@ -87,13 +85,12 @@ Mota.require('var', 'loading').once('coreInit', () => { const gl = shader.gl; shader.size(480, 480); shader.setHD(true); + shader.setZIndex(100); RainWeather.shader = shader; - const program = shader.createProgram(); - program.setVersion(shader.VERSION_ES_300); + const program = shader.createProgram(ShaderProgram); program.fs(rainFs); program.vs(rainVs); program.requestCompile(); - program.useDefault(false); const pos = program.defineAttribArray('a_rainVertex'); program.defineAttribArray('a_offset'); program.defineAttribArray('a_data'); @@ -108,13 +105,6 @@ Mota.require('var', 'loading').once('coreInit', () => { pos.pointer(2, gl.FLOAT, false, 0, 0); pos.enable(); } - - const back = shader.createProgram(); - back.requestCompile(); - back.modified = true; - back.useDefault(false); - RainShader.backProgram = back; - shader.useProgram(back); }); export class RainWeather implements IWeather { @@ -129,10 +119,8 @@ export class RainWeather implements IWeather { activate(): void { const render = MotaRenderer.get('render-main'); const draw = render?.getElementById('map-draw') as Container; - const layer = draw.children; - if (!layer || !draw) return; + if (!draw) return; const shader = RainWeather.shader; - shader.appendChild(...layer); shader.append(draw); const gl = shader.gl; @@ -222,20 +210,4 @@ class RainShader extends Shader { program.paramArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, num); color?.set(1, 1, 1, 0.1); } - - protected override preDraw(gl: WebGL2RenderingContext): boolean { - const back = RainShader.backProgram; - const rain = RainShader.rainProgram; - this.useProgram(back); - const ready = this.defaultReady(); - const param = back.getDrawParams(this.DRAW_ELEMENTS); - const rainParam = rain.getDrawParams(this.DRAW_ARRAYS_INSTANCED); - if (!ready || !param || !rainParam) return false; - - this.draw(gl, back, param, back.usingIndices); - this.useProgram(rain); - if (this.defaultReady()) this.draw(gl, rain, rainParam, null); - - return false; - } } diff --git a/src/module/weather/snow.ts b/src/module/weather/snow.ts index 1b8e61c..a0bddcf 100644 --- a/src/module/weather/snow.ts +++ b/src/module/weather/snow.ts @@ -1,12 +1,10 @@ -import { - IShaderUniform, - Shader, - ShaderProgram, - UniformType -} from '@/core/render/shader'; +import { Shader, ShaderProgram } from '@/core/render/shader'; import { IWeather, WeatherController } from './weather'; import { MotaRenderer } from '@/core/render/render'; import { Container } from '@/core/render/container'; +import { GL2Program, IShaderUniform, UniformType } from '@/core/render/gl2'; +import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d'; +import { Transform } from '@/core/render/transform'; const snowVs = /* glsl */ ` in vec2 a_snowVertex; @@ -108,12 +106,10 @@ Mota.require('var', 'loading').once('coreInit', () => { shader.size(480, 480); shader.setHD(true); SnowWeather.shader = shader; - const program = shader.createProgram(); - program.setVersion(shader.VERSION_ES_300); + const program = shader.createProgram(ShaderProgram); program.fs(snowFs); program.vs(snowVs); program.requestCompile(); - program.useDefault(false); const pos = program.defineAttribArray('a_snowVertex'); program.defineAttribArray('a_offset'); program.defineAttribArray('a_data'); @@ -128,13 +124,6 @@ Mota.require('var', 'loading').once('coreInit', () => { pos.pointer(2, gl.FLOAT, false, 0, 0); pos.enable(); } - - const back = shader.createProgram(); - back.requestCompile(); - back.modified = true; - back.useDefault(false); - SnowShader.backProgram = back; - shader.useProgram(back); }); export class SnowWeather implements IWeather { @@ -148,11 +137,9 @@ export class SnowWeather implements IWeather { activate(): void { const render = MotaRenderer.get('render-main'); - const layer = render?.getElementById('layer-main'); const draw = render?.getElementById('map-draw') as Container; - if (!layer || !draw) return; + if (!draw) return; const shader = SnowWeather.shader; - layer.append(shader); shader.append(draw); const gl = shader.gl; @@ -243,19 +230,18 @@ class SnowShader extends Shader { color?.set(1, 1, 1, 0.1); } - protected override preDraw(gl: WebGL2RenderingContext): boolean { - const back = SnowShader.backProgram; + protected preDraw( + canvas: MotaOffscreenCanvas2D, + transform: Transform, + gl: WebGL2RenderingContext, + program: GL2Program + ): boolean { const snow = SnowShader.snowProgram; - this.useProgram(back); - const ready = this.defaultReady(); - const param = back.getDrawParams(this.DRAW_ELEMENTS); const snowParam = snow.getDrawParams(this.DRAW_ARRAYS_INSTANCED); - if (!ready || !param || !snowParam) return false; - - this.draw(gl, back, param, back.usingIndices); + if (!snowParam) return false; this.useProgram(snow); - this.draw(gl, snow, snowParam, null); - + if (!snow.ready()) return false; + this.draw(gl, snow); return false; } } diff --git a/src/plugin/boss/towerBoss.ts b/src/plugin/boss/towerBoss.ts index ab88a14..8729f09 100644 --- a/src/plugin/boss/towerBoss.ts +++ b/src/plugin/boss/towerBoss.ts @@ -28,6 +28,7 @@ Mota.require('var', 'loading').once('coreInit', () => { const shader = new Shader(); shader.size(480, 480); shader.setHD(true); + shader.setZIndex(120); TowerBoss.shader = shader; TowerBoss.effect.create(shader, 40); }); @@ -151,8 +152,6 @@ export class TowerBoss extends BarrageBoss { override start() { super.start(); - this.group.remove(); - this.group.append(TowerBoss.shader); TowerBoss.shader.append(this.mapDraw); this.healthBar.append(this.group); this.word.append(this.group); @@ -177,7 +176,6 @@ export class TowerBoss extends BarrageBoss { override end() { super.end(); TowerBoss.shader.remove(); - this.group.append(this.mapDraw); this.healthBar.remove(); this.word.remove(); this.main.remove(); diff --git a/src/plugin/fx/pointShader.ts b/src/plugin/fx/pointShader.ts index 792f2be..ea759b3 100644 --- a/src/plugin/fx/pointShader.ts +++ b/src/plugin/fx/pointShader.ts @@ -1,5 +1,6 @@ import { logger } from '@/core/common/logger'; -import { Shader, ShaderProgram, UniformType } from '@/core/render/shader'; +import { UniformType } from '@/core/render/gl2'; +import { Shader, ShaderProgram } from '@/core/render/shader'; import { Transform } from '@/core/render/transform'; export const enum PointEffectType { @@ -118,8 +119,7 @@ export class PointEffect { */ create(shader: Shader, itemCount: number) { if (this.program || this.shader || this.started) return; - const program = shader.createProgram(); - program.setVersion(shader.VERSION_ES_300); + const program = shader.createProgram(ShaderProgram); program.fs(generateFragment(itemCount)); program.requestCompile(); diff --git a/src/plugin/game/fallback.ts b/src/plugin/game/fallback.ts index 17ef2af..c266574 100644 --- a/src/plugin/game/fallback.ts +++ b/src/plugin/game/fallback.ts @@ -7,7 +7,12 @@ import type { import type { HeroRenderer } from '@/core/render/preset/hero'; import type { Layer, LayerGroup } from '@/core/render/preset/layer'; import type { TimingFn } from 'mutate-animate'; -import { BlockMover, heroMoveCollection, MoveStep } from '@/game/state/move'; +import { + BlockMover, + heroMoveCollection, + IMoveController, + MoveStep +} from '@/game/state/move'; import type { FloorViewport } from '@/core/render/preset/viewport'; // 向后兼容用,会充当两个版本间过渡的作用 @@ -647,26 +652,30 @@ export function init() { } }); + const moveAction = new Set(['up', 'down', 'left', 'right']); + let controller: IMoveController | null = null; // 复写录像的移动 core.registerReplayAction('move', action => { - if ( - action === 'up' || - action === 'down' || - action === 'left' || - action === 'right' - ) { - // const { noPass, canMove } = checkCanMove(); - // const { nx, ny } = getNextLoc(); - // if (noPass || !canMove) { - // if (canMove) core.trigger(nx, ny); - // } else { - // core.setHeroLoc('x', nx); - // core.setHeroLoc('y', ny); - // core.setHeroLoc('direction', action); - // } - - // setTimeout(core.replay, 100); - + if (moveAction.has(action)) { + const next = core.status.replay.toReplay[1]; + if (!heroMover.moving) { + controller = heroMover.startMove(); + } + if (!controller) { + return false; + } + controller.push({ + type: 'dir', + value: action as Dir + }); + if (moveAction.has(next)) { + core.replay(); + } else { + controller.onEnd.then(() => { + core.replay(); + controller = null; + }); + } return true; } else { return false;