Files
template/packages/render/src/core/gl2.ts
T
2026-03-16 12:23:02 +08:00

1387 lines
47 KiB
TypeScript

import { logger } from '@motajs/common';
import { MotaOffscreenCanvas2D } from './canvas2d';
import { RenderItem } from './item';
import { Transform } from './transform';
import { isWebGL2Supported } from './utils';
import { SizedCanvasImageSource } from '../types';
import {
AttribSetFn,
AttribType,
DrawArraysInstancedParam,
DrawArraysParam,
DrawElementsInstancedParam,
DrawElementsParam,
DrawParamsMap,
IGL2Program,
IGL2ProgramPrefix,
IShaderAttrib,
IShaderAttribArray,
IShaderIndices,
IShaderTexture2D,
IShaderUniform,
IShaderUniformBlock,
IShaderUniformMatrix,
IWebGL2RenderItem,
ProgramConstructor,
RenderMode,
UniformMatrix,
UniformSetFn,
UniformType
} from './types';
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;
}
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: IGL2Program | null = null;
/** 当前渲染实例的所有着色器程序 */
protected programs: Set<IGL2Program> = new Set();
/** framebuffer 映射 */
protected framebufferMap: Map<string, WebGLFramebuffer> = new Map();
constructor() {
super(false);
this.canvas = document.createElement('canvas');
const gl = this.canvas.getContext('webgl2')!;
this.gl = gl;
if (!GL2.support) {
this.canvas.width = 0;
this.canvas.height = 0;
} else {
const num = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
if (typeof num === 'number') {
this.MAX_TEXTURE_COUNT = num;
}
}
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
this.init();
}
private init() {
const gl = this.gl;
if (!gl) return;
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.depthFunc(gl.LEQUAL);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
}
onResize(scale: number): void {
super.onResize(scale);
this.sizeGL(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 * this.scale;
this.canvas.width = width * scale;
this.canvas.height = height * scale;
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
}
protected render(
canvas: MotaOffscreenCanvas2D,
transform: Transform
): void {
if (!GL2.support || !this.program || !this.gl) return;
const compile = this.program.requestCompile();
if (compile) {
this.gl.useProgram(this.program.program);
}
// 清空画布
const gl = this.gl;
gl.clearColor(0, 0, 0, 0);
gl.clearDepth(1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
this.drawScene(canvas, gl, transform);
canvas.clear();
canvas.ctx.drawImage(this.canvas, 0, 0, this.width, this.height);
}
/**
* 渲染当前 gl2 画布
* @param canvas 渲染至的目标画布,注意系统会自动将 gl2 画布渲染至目标画布,不需要手动画到该画布上
* @param gl 当前正在渲染的 gl2 画布
* @param program 当前元素正在使用的着色器程序
* @param transform 当前元素相对父元素的变换矩阵
*/
protected abstract drawScene(
canvas: MotaOffscreenCanvas2D,
gl: WebGL2RenderingContext,
transform: Transform
): void;
/**
* 执行顶点绘制
* @param gl 当前正在渲染的 gl2 画布
* @param program 当前元素正在使用的着色器程序
*/
protected draw(gl: WebGL2RenderingContext, program: IGL2Program) {
const indices = program.usingIndices;
const param = program.getDrawParams(program.renderMode);
if (!param) return;
program.ready();
switch (program.renderMode) {
case RenderMode.Arrays: {
const { mode, first, count } = param as DrawArraysParam;
gl.drawArrays(mode, first, count);
break;
}
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);
break;
}
case RenderMode.ArraysInstanced: {
const { mode, first, count, instanceCount } =
param as DrawArraysInstancedParam;
gl.drawArraysInstanced(mode, first, count, instanceCount);
break;
}
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);
break;
}
}
}
/**
* 将画面渲染至帧缓冲
* @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.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: IGL2Program) {
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<T extends IGL2Program>(
Program: ProgramConstructor<T>,
vs?: string,
fs?: string
): T {
const program = new Program(this, vs, fs);
this.programs.add(program);
return program;
}
/**
* 删除一个着色器程序
* @param program 要删除的着色器程序
*/
deleteProgram(program: IGL2Program) {
if (program.element !== this) {
logger.error(18);
return;
}
program.destroy();
this.programs.delete(program);
}
destroy(): void {
this.programs.forEach(v => v.destroy());
this.programs.clear();
this.canvas.remove();
super.destroy();
}
}
class ShaderUniform<T extends UniformType> implements IShaderUniform<T> {
constructor(
readonly type: T,
readonly location: WebGLUniformLocation,
readonly gl: WebGL2RenderingContext,
readonly program: IGL2Program
) {}
set(...params: UniformSetFn[T]): void {
// 因为ts类型推导的限制,类型肯定正确,但是推导不出,所以这里直接 as any 屏蔽掉类型推导
this.gl.useProgram(this.program.program);
const [x0, x1, x2, x3] = params 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<T extends AttribType> implements IShaderAttrib<T> {
constructor(
readonly type: T,
readonly location: number,
readonly gl: WebGL2RenderingContext,
readonly program: IGL2Program
) {}
set(...params: AttribSetFn[T]) {
// 因为ts类型推导的限制,类型肯定正确,但是推导不出,所以这里直接 as any 屏蔽掉类型推导
this.gl.useProgram(this.program.program);
const [x0, x1, x2, x3] = params 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: IGL2Program
) {}
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.useProgram(this.program.program);
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.useProgram(this.program.program);
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.useProgram(this.program.program);
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.useProgram(this.program.program);
gl.bindBuffer(gl.ARRAY_BUFFER, this.data);
gl.vertexAttribIPointer(this.location, size, type, stride, offset);
}
divisor(divisor: number): void {
const gl = this.gl;
gl.useProgram(this.program.program);
gl.vertexAttribDivisor(this.location, divisor);
}
enable(): void {
this.gl.useProgram(this.program.program);
this.gl.enableVertexAttribArray(this.location);
}
disable(): void {
this.gl.useProgram(this.program.program);
this.gl.disableVertexAttribArray(this.location);
}
}
class ShaderIndices implements IShaderIndices {
constructor(
readonly data: WebGLBuffer,
readonly gl: WebGL2RenderingContext,
readonly program: IGL2Program
) {}
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.useProgram(this.program.program);
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.useProgram(this.program.program);
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: IGL2Program
) {}
set(x2: GLboolean, x3: Float32List, x4?: number, x5?: number): void {
this.gl.useProgram(this.program.program);
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: IGL2Program
) {}
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;
gl.useProgram(this.program.program);
const buffer = this.buffer;
gl.bindBuffer(gl.UNIFORM_BUFFER, buffer);
if (srcOffset !== void 0) {
// @ts-expect-error 无法推断
gl.bufferSubData(gl.UNIFORM_BUFFER, 0, srcData, srcOffset, length);
} else {
// @ts-expect-error 无法推断
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<UniformType.Uniform1i>,
readonly gl: WebGL2RenderingContext,
readonly program: IGL2Program,
public width: number = 0,
public height: number = 0
) {
uniform.set(index);
}
set(source: TexImageSource): void {
const gl = this.gl;
gl.useProgram(this.program.program);
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.useProgram(this.program.program);
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
);
}
}
export class GL2Program implements IGL2Program {
/** 顶点着色器 */
private vertex: string = '';
/** 片元着色器 */
private fragment: string = '';
/** webgl2上下文 */
gl: WebGL2RenderingContext;
/** 当前着色器程序的着色器渲染元素 */
element: IWebGL2RenderItem;
/** uniform存放地址 */
private uniform: Map<string, IShaderUniform<UniformType>> = new Map();
/** attribute存放地址,300版本里面叫做in */
private attribute: Map<string, IShaderAttrib<AttribType>> = new Map();
/** attribute array 存放地址 */
private attribArray: Map<string, IShaderAttribArray> = new Map();
/** 顶点索引存放地址 */
private indices: Map<string, IShaderIndices> = new Map();
/** uniform矩阵存放地址 */
private matrix: Map<string, IShaderUniformMatrix> = new Map();
/** uniform block 存放地址 */
private block: Map<string, IShaderUniformBlock> = new Map();
/** 纹理存放地址 */
private texture: Map<string, IShaderTexture2D> = new Map();
/** 当前编译完成的shader程序 */
private shader: CompiledShader | null = null;
/** 当前的webgl程序 */
program: WebGLProgram | null = null;
/** 当前正在使用的顶点索引数组 */
usingIndices: IShaderIndices | null = null;
/** 渲染模式 */
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: IWebGL2RenderItem, vs?: string, fs?: string) {
if (vs) this.vs(vs);
if (fs) this.fs(fs);
this.element = shader;
this.gl = shader.gl;
if (vs || fs) this.requestCompile();
}
/**
* 渲染前准备
*/
ready(): boolean {
return true;
}
/**
* 设置渲染模式,目前可选 {@link GL2.DRAW_ARRAYS} 至 {@link GL2.DRAW_INSTANCED}
*/
mode(mode: RenderMode) {
this.renderMode = mode;
}
/**
* 获取指定渲染模式的渲染参数
* @param param 渲染模式
*/
getDrawParams<T extends RenderMode>(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];
}
}
/**
* 设置 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 是否强制重新编译
* @returns 是否执行了编译操作
*/
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;
}
/**
* 设置片元着色器内容
* @param fs 片元着色器
*/
fs(fs: string) {
this.fragment = this.prefix.FRAGMENT + fs;
this.shaderDirty = true;
}
/**
* 当这个程序被卸载时执行的函数
*/
unload() {
this.attribArray.forEach(v => {
v.disable();
});
}
/**
* 当这个程序被加载(使用)时执行的函数
*/
load() {
this.attribArray.forEach(v => {
v.enable();
});
}
/**
* 获取一个uniform,需要事先定义,否则返回null
* @param uniform uniform名称
*/
getUniform<T extends UniformType = UniformType>(
uniform: string
): IShaderUniform<T> | null {
return (this.uniform.get(uniform) as IShaderUniform<T>) ?? null;
}
/**
* 获取一个attribute,需要事先定义,否则返回null
* @param attrib attribute名称
*/
getAttribute<T extends AttribType = AttribType>(
attrib: string
): IShaderAttrib<T> | null {
return (this.attribute.get(attrib) as IShaderAttrib<T>) ?? 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 GL2.UNIFORM_1f} 至 {@link GL2.UNIFORM_4uiv}
* @returns uniform 变量的操作对象,可用于设置其值
*/
defineUniform<T extends UniformType>(
uniform: string,
type: T
): IShaderUniform<T> | null {
const u = this.getUniform<T>(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 GL2.U_MATRIX_2x2} 至 {@link GL2.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 GL2.ATTRIB_1f} 至 {@link GL2.ATTRIB_I4uiv}
* @returns attribute 常量的操作对象,可用于设置其值
*/
defineAttribute<T extends AttribType>(
attrib: string,
type: T
): IShaderAttrib<T> | null {
const u = this.getAttribute<T>(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;
}
/**
* 绑定纹理,自动判断应该使用 sub 还是 set
* @param program 使用的着色器程序
* @param texture 要绑定至的纹理
* @param source 纹理内容
* @returns 是否绑定成功
*/
texTexture(texture: string, source: SizedCanvasImageSource) {
const tex = this.getTexture(texture);
if (!tex) return false;
if (tex.width === source.width && tex.height === source.height) {
tex.sub(source, 0, 0, source.width, source.height);
} else {
tex.set(source);
}
return true;
}
/**
* 摧毁这个着色器程序,不要直接调用,请使用 {@link GL2.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;
}
}