HumanBreak/src/core/render/shader.ts

1808 lines
59 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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';
const SHADER_VERTEX_PREFIX_300 = /* glsl */ `#version 300 es
precision highp float;
in vec4 a_position;
in vec2 a_texCoord;
out highp vec2 v_texCoord;
`;
const SHADER_VERTEX_PREFIX_100 = /* glsl */ `
precision highp float;
attribute vec4 a_position;
attribute vec2 a_texCoord;
varying highp vec2 v_texCoord;
`;
const SHADER_FRAGMENT_PREFIX_300 = /* glsl */ `#version 300 es
precision highp float;
in highp vec2 v_texCoord;
uniform sampler2D u_sampler;
`;
const SHADER_FRAGMENT_PREFIX_100 = /* glsl */ `
precision highp float;
varying highp vec2 v_texCoord;
uniform sampler2D u_sampler;
`;
const DEFAULT_VS = /* glsl */ `
void main() {
v_texCoord = a_texCoord;
gl_Position = a_position;
}
`;
const DEFAULT_FS = /* glsl */ `
void main() {
gl_FragColor = texture2D(u_sampler, v_texCoord);
}
`;
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<EShaderEvent> {
/** 是否支持此组件 */
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<ShaderProgram> = new Set();
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<any>): void {
const isSelf = item === this && !this.cacheDirty;
super.update(item);
if (isSelf) this.cacheDirty = false;
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;
if (!indices) return;
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();
if (!pre) {
this.postDraw();
return;
}
switch (program.renderMode) {
case RenderMode.Arrays: {
const { mode, first, count } = param as DrawArraysParam;
gl.drawArrays(mode, first, count);
}
case RenderMode.Elements: {
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: {
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);
}
}
this.postDraw();
}
/**
* 在本着色器内部渲染之前执行的渲染如果返回false则表示不进行内部渲染但依然会执行 {@link postDraw}。
* 继承本类,并复写此方法即可实现前置渲染功能
*/
protected preDraw(): boolean {
return true;
}
/**
* 在本着色器内部渲染之后执行的渲染即使preDraw返回false本函数也会执行
* 继承本类,并复写此方法即可实现后置渲染功能
*/
protected postDraw() {}
private 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 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.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<T> = [data: T, srcOffset?: number, srcLength?: number];
type _A<T> = [data: T];
interface UniformSetFn {
[UniformType.Uniform1f]: _U1;
[UniformType.Uniform1fv]: _UV<Float32List>;
[UniformType.Uniform1i]: _U1;
[UniformType.Uniform1iv]: _UV<Int32List>;
[UniformType.Uniform1ui]: _U1;
[UniformType.Uniform1uiv]: _UV<Uint32List>;
[UniformType.Uniform2f]: _U2;
[UniformType.Uniform2fv]: _UV<Float32List>;
[UniformType.Uniform2i]: _U2;
[UniformType.Uniform2iv]: _UV<Int32List>;
[UniformType.Uniform2ui]: _U2;
[UniformType.Uniform2uiv]: _UV<Uint32List>;
[UniformType.Uniform3f]: _U3;
[UniformType.Uniform3fv]: _UV<Float32List>;
[UniformType.Uniform3i]: _U3;
[UniformType.Uniform3iv]: _UV<Int32List>;
[UniformType.Uniform3ui]: _U3;
[UniformType.Uniform3uiv]: _UV<Uint32List>;
[UniformType.Uniform4f]: _U4;
[UniformType.Uniform4fv]: _UV<Float32List>;
[UniformType.Uniform4i]: _U4;
[UniformType.Uniform4iv]: _UV<Int32List>;
[UniformType.Uniform4ui]: _U4;
[UniformType.Uniform4uiv]: _UV<Uint32List>;
}
interface AttribSetFn {
[AttribType.Attrib1f]: _U1;
[AttribType.Attrib1fv]: _A<Float32List>;
[AttribType.Attrib2f]: _U2;
[AttribType.Attrib2fv]: _A<Float32List>;
[AttribType.Attrib3f]: _U3;
[AttribType.Attrib3fv]: _A<Float32List>;
[AttribType.Attrib4f]: _U4;
[AttribType.Attrib4fv]: _A<Float32List>;
[AttribType.AttribI4i]: _U4;
[AttribType.AttribI4iv]: _A<Int32List>;
[AttribType.AttribI4ui]: _U4;
[AttribType.AttribI4uiv]: _A<Uint32List>;
}
interface IShaderUniform<T extends UniformType> {
/** 这个 uniform 变量的内存位置 */
readonly location: WebGLUniformLocation;
/** 这个 uniform 变量的类型 */
readonly type: T;
/**
* 设置这个 uniform 变量的值,
* 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL2RenderingContext/uniform
* @param params 要传递的参数,例如 uniform2f 就要传递 x0 x1 两个参数等,可以参考 mdn 文档
*/
set(...params: UniformSetFn[T]): void;
}
interface IShaderAttrib<T extends AttribType> {
/** 这个 attribute 常量的内存位置 */
readonly location: number;
/** 这个 attribute 常量的类型 */
readonly type: T;
/**
* 设置这个 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;
}
interface IShaderAttribArray {
/** 这个 attribute 常量的内存位置 */
readonly location: number;
/** 这个 attribute 所用的缓冲区信息 */
readonly data: WebGLBuffer;
/**
* 修改缓冲区数据,会更改数据大小,重新分配内存,不更改数据大小的情况下建议使用 {@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;
}
interface IShaderIndices {
/** 这个顶点索引所用的缓冲区信息 */
readonly data: WebGLBuffer;
/**
* 修改缓冲区数据,会更改数据大小,重新分配内存,不更改数据大小的情况下建议使用 {@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;
}
interface IShaderUniformMatrix {
/** 矩阵的内存位置 */
readonly location: WebGLUniformLocation;
/** 矩阵类型 */
readonly type: UniformMatrix;
/**
* 设置矩阵的值,参考 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;
}
interface IShaderUniformBlock {
/** 这个 uniform block 的内存地址 */
location: GLuint;
/** 与这个 uniform block 所绑定的缓冲区 */
buffer: WebGLBuffer;
/** 这个 uniform block 的大小 */
size: number;
/**
* 参考 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;
}
interface IShaderTexture2D {
/** 纹理对象 */
readonly texture: WebGLTexture;
/** 宽度 */
readonly width: number;
/** 高度 */
readonly height: number;
/** 纹理所属索引 */
readonly index: number;
/**
* 设置这个纹理的图像,不建议使用,会修改宽高
* @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<T extends UniformType> implements IShaderUniform<T> {
constructor(
readonly type: T,
readonly location: WebGLUniformLocation,
readonly gl: WebGL2RenderingContext
) {}
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<T extends AttribType> implements IShaderAttrib<T> {
constructor(
readonly type: T,
readonly location: number,
readonly gl: WebGL2RenderingContext
) {}
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
) {}
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
) {}
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
) {}
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
) {}
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<UniformType.Uniform1i>,
readonly gl: WebGL2RenderingContext,
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;
}
interface DrawParamsMap {
[RenderMode.Arrays]: DrawArraysParam;
[RenderMode.ArraysInstanced]: DrawArraysInstancedParam;
[RenderMode.Elements]: DrawElementsParam;
[RenderMode.ElementsInstanced]: DrawElementsInstancedParam;
}
interface ShaderProgramEvent {
load: [];
unload: [];
}
export class ShaderProgram extends EventEmitter<ShaderProgramEvent> {
/** 顶点着色器 */
private vertex: string = DEFAULT_VS;
/** 片元着色器 */
private fragment: string = DEFAULT_FS;
/** glsl版本 */
version: ShaderVersion;
/** webgl2上下文 */
gl: WebGL2RenderingContext;
/** 当前着色器程序的着色器渲染元素 */
element: Shader;
/** 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;
/** 准备函数 */
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<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];
}
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;
} else {
logger.warn(31);
}
}
}
/**
* 设置着色器使用的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();
});
this.emit('load');
}
/**
* 当这个程序被加载(使用)时执行的函数
*/
load() {
this.attribArray.forEach(v => {
v.enable();
});
this.emit('unload');
}
/**
* 获取一个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 Shader.UNIFORM_1f} 至 {@link Shader.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.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.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<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.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);
const obj = new ShaderAttribArray(buffer, location, gl);
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.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 location = gl.getUniformBlockIndex(program, block);
if (location === -1) return null;
const buffer = gl.createBuffer();
if (!buffer) return null;
const data = new Float32Array(size);
data.fill(0);
gl.bindBuffer(gl.UNIFORM_BUFFER, buffer);
gl.bufferData(gl.UNIFORM_BUFFER, data, usage);
gl.uniformBlockBinding(program, location, binding);
gl.bindBufferBase(gl.UNIFORM_BUFFER, binding, buffer);
const obj = new ShaderUniformBlock(location, size, buffer, binding, gl);
this.block.set(block, obj);
return obj;
}
/**
* 定义一个材质
* @param name 纹理名称
* @param index 纹理索引,根据不同浏览器,其最大数量不一定相等,根据标准其数量应该大于等于 8 个,
* 因此考虑到兼容性,不建议纹理数量超过 8 个。
* @param width 纹理的宽度
* @param height 纹理的高度
* @returns 这个 texture 的操作对象,可以用于设置其内容
*/
defineTexture(
name: string,
index: number,
width?: number,
height?: 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 uniform = this.defineUniform(name, UniformType.Uniform1i);
if (!uniform) 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, uniform, gl, width, height);
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();
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);
const indices = this.defineIndices('defalutIndices');
if (!tex || !position || !sampler || !indices) {
return false;
}
position.buffer(
new Float32Array([1, -1, -1, -1, 1, 1, -1, 1]),
gl.STATIC_DRAW
);
position.pointer(2, gl.FLOAT, false, 0, 0);
position.enable();
tex.buffer(new Float32Array([1, 1, 0, 1, 1, 0, 0, 0]), gl.STATIC_DRAW);
tex.pointer(2, gl.FLOAT, false, 0, 0);
tex.enable();
indices.buffer(new Uint16Array([0, 1, 2, 2, 3, 1]), gl.STATIC_DRAW);
this.useIndices(indices);
this.paramElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
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;
}
}