Skip to content

Commit 52ae49d

Browse files
committed
Updated shader hooks
No longer have to pass logic as strings
1 parent 8b3da11 commit 52ae49d

File tree

4 files changed

+97
-89
lines changed

4 files changed

+97
-89
lines changed

src/core/base.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
import { Utils } from './utils'
22

3+
export const enum MethodName {
4+
TOUCH,
5+
INIT,
6+
LOOP,
7+
RENDER,
8+
RESIZE,
9+
INPUT
10+
}
11+
312
export class domHandler {
413
private _container: HTMLCanvasElement;
514
private sectionSize: { width: number, height: number } = { width: 0, height: 0 };

src/core/index.ts

Lines changed: 68 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,35 @@
1-
import { LogicFns, UniformValue, shaderArgs, LogicProcesses } from "../types";
2-
import { domHandler } from "./base";
1+
import { shaderArgs, UniformValue, ShaderHook } from "../types";
2+
import { domHandler, MethodName } from "./base";
3+
34

45
/** Shader Class
56
* @export
67
* @class Shader
78
* @extends {domHandler}
8-
* @param {LogicFns} logic
9+
*
910
*/
10-
export class SimpleShaderCanvas extends domHandler {
11-
private logic: LogicFns = {};
12-
private gl: WebGLRenderingContext | null;
11+
export class Shader extends domHandler {
12+
private hooks: Record<string, ShaderHook[]> = {};
13+
private gl: WebGLRenderingContext;
1314
private shaderProgram: WebGLProgram;
1415
private vertexBuffer: WebGLBuffer;
1516
private uniforms: Array<UniformValue> | undefined
1617

1718
constructor(container: HTMLCanvasElement, args: shaderArgs) {
1819
super(container);
19-
this.gl = container.getContext('webgl');
20-
if (!this.gl) throw new Error('No shader context!')
21-
20+
this.gl = container.getContext('webgl') as WebGLRenderingContext;
2221
this.shaderProgram = this.initializeShader(args.vertShader, args.fragShader);
23-
2422
this.vertexBuffer = this.initBuffers();
2523
this.uniforms = args.uniforms
26-
2724
// Initialize custom logic if provided
28-
if (args.logic) this.initializeLogic(args.logic);
25+
args.hooks?.forEach((hook) => {
26+
this.addHook(hook.methodName, hook.hook)
27+
})
2928
}
3029

3130
public init() {
3231
super.init();
33-
this.logic.init?.(this);
32+
this.runHooks(MethodName.INIT);
3433

3534
this.resize()
3635
this.startLoop(60);
@@ -40,12 +39,15 @@ export class SimpleShaderCanvas extends domHandler {
4039
})
4140
}
4241

43-
// Initializes custom logic from provided args
44-
private initializeLogic(logic: { [key in LogicProcesses]?: string }): void {
45-
Object.entries(logic).forEach(([key, logicFunction]) => {
46-
const logicFn = new Function(`return ${logicFunction}`)() as (shader: SimpleShaderCanvas) => void;
47-
this.logic[key as LogicProcesses] = logicFn;
48-
});
42+
public addHook(methodName: MethodName, hook: ShaderHook): void {
43+
if (!this.hooks[methodName]) this.hooks[methodName] = [];
44+
this.hooks[methodName].push(hook);
45+
}
46+
47+
protected runHooks(method: MethodName, ...args: any[]) {
48+
if (this.hooks[method]) {
49+
this.hooks[method].forEach((hook) => hook(this, ...args));
50+
}
4951
}
5052

5153
private initBuffers(): WebGLBuffer {
@@ -59,18 +61,16 @@ export class SimpleShaderCanvas extends domHandler {
5961
1.0, 1.0 // Top-right
6062
]);
6163

62-
const vertexBuffer = this.gl?.createBuffer();
64+
const vertexBuffer = this.gl.createBuffer();
6365
if (!vertexBuffer) throw new Error('Failed to create vertex buffer');
6466

65-
this.gl?.bindBuffer(this.gl.ARRAY_BUFFER, vertexBuffer);
66-
this.gl?.bufferData(this.gl.ARRAY_BUFFER, vertices, this.gl.STATIC_DRAW);
67+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, vertexBuffer);
68+
this.gl.bufferData(this.gl.ARRAY_BUFFER, vertices, this.gl.STATIC_DRAW);
6769

6870
return vertexBuffer;
6971
}
70-
71-
private loadShader(gl: WebGLRenderingContext | null, type?: GLenum, source: string = ''): WebGLShader | null {
72-
if (!gl || !type) return null;
7372

73+
private loadShader(gl: WebGLRenderingContext, type: GLenum, source: string = ''): WebGLShader | null {
7474
const shader = gl.createShader(type);
7575
if (!shader) return null;
7676

@@ -87,19 +87,19 @@ export class SimpleShaderCanvas extends domHandler {
8787
}
8888

8989
private initializeShader(vertShader?: string, fragShader?: string): WebGLProgram {
90-
const vertexShader = this.loadShader(this.gl, this.gl?.VERTEX_SHADER, vertShader);
91-
const fragmentShader = this.loadShader(this.gl, this.gl?.FRAGMENT_SHADER, fragShader);
90+
const vertexShader = this.loadShader(this.gl, this.gl.VERTEX_SHADER, vertShader);
91+
const fragmentShader = this.loadShader(this.gl, this.gl.FRAGMENT_SHADER, fragShader);
9292
if (!vertexShader || !fragmentShader) throw new Error('Shader compilation failed');
9393

94-
const shaderProgram = this.gl?.createProgram();
94+
const shaderProgram = this.gl.createProgram();
9595
if (!shaderProgram) throw new Error('Failed to create shader program');
9696

97-
this.gl?.attachShader(shaderProgram, vertexShader);
98-
this.gl?.attachShader(shaderProgram, fragmentShader);
99-
this.gl?.linkProgram(shaderProgram);
97+
this.gl.attachShader(shaderProgram, vertexShader);
98+
this.gl.attachShader(shaderProgram, fragmentShader);
99+
this.gl.linkProgram(shaderProgram);
100100

101-
if (!this.gl?.getProgramParameter(shaderProgram, this.gl.LINK_STATUS)) {
102-
console.error('Shader program link error:', this.gl?.getProgramInfoLog(shaderProgram));
101+
if (!this.gl.getProgramParameter(shaderProgram, this.gl.LINK_STATUS)) {
102+
console.error('Shader program link error:', this.gl.getProgramInfoLog(shaderProgram));
103103
throw new Error('Unable to initialize the shader program');
104104
}
105105
return shaderProgram;
@@ -108,40 +108,42 @@ export class SimpleShaderCanvas extends domHandler {
108108
// Main render loop
109109
protected loop(): void {
110110
super.loop();
111-
this.logic.loop?.(this);
111+
this.runHooks(MethodName.LOOP);
112112
this.render();
113113
}
114114

115115
protected render(): void {
116116
const gl = this.gl;
117-
gl?.clear(gl.COLOR_BUFFER_BIT);
118-
gl?.clear(gl.DEPTH_BUFFER_BIT);
119-
gl?.clear(gl.STENCIL_BUFFER_BIT);
117+
this.runHooks(MethodName.RENDER);
118+
119+
gl.clear(gl.COLOR_BUFFER_BIT);
120+
gl.clear(gl.DEPTH_BUFFER_BIT);
121+
gl.clear(gl.STENCIL_BUFFER_BIT);
120122

121-
const vertexPosition = gl?.getAttribLocation(this.shaderProgram, 'a_position');
122-
gl?.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
123-
if (vertexPosition) gl?.vertexAttribPointer(vertexPosition, 2, gl.FLOAT, false, 0, 0);
124-
if (vertexPosition) gl?.enableVertexAttribArray(vertexPosition);
123+
const vertexPosition = gl.getAttribLocation(this.shaderProgram, 'a_position');
124+
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
125+
gl.vertexAttribPointer(vertexPosition, 2, gl.FLOAT, false, 0, 0);
126+
gl.enableVertexAttribArray(vertexPosition);
125127

126-
gl?.useProgram(this.shaderProgram);
127-
gl?.drawArrays(gl.TRIANGLES, 0, 6);
128+
gl.useProgram(this.shaderProgram);
129+
gl.drawArrays(gl.TRIANGLES, 0, 6);
128130
}
129131

130132
/** Gets a uniform value from the shader */
131-
public getUniform(name: string) {
132-
const uLoc = this.gl?.getUniformLocation(this.shaderProgram, name);
133+
public getUniform(name: string): UniformValue | undefined {
134+
const uLoc = this.gl.getUniformLocation(this.shaderProgram, name);
133135
if (!uLoc) {
134136
console.error(`Uniform ${name} not found.`);
135137
return;
136138
}
137139

138-
return this.gl?.getUniform(this.shaderProgram, uLoc)
140+
return this.gl.getUniform(this.shaderProgram, uLoc)
139141
}
140142

141143
/** Sets a uniform value for the shader */
142144
public setUniform(uniform: UniformValue): void {
143145
const { name, type, value } = uniform
144-
const uLoc = this.gl?.getUniformLocation(this.shaderProgram, name);
146+
const uLoc = this.gl.getUniformLocation(this.shaderProgram, name);
145147

146148
if (!uLoc) {
147149
console.error(`Uniform ${name} not found.`);
@@ -150,37 +152,37 @@ export class SimpleShaderCanvas extends domHandler {
150152

151153
switch (type) {
152154
case "float":
153-
this.gl?.uniform1f(uLoc, value as number);
155+
this.gl.uniform1f(uLoc, value as number);
154156
break;
155157
case "vec2":
156-
this.gl?.uniform2fv(uLoc, value as Float32Array);
158+
this.gl.uniform2fv(uLoc, value as Float32Array);
157159
break;
158160
case "vec3":
159-
this.gl?.uniform3fv(uLoc, value as Float32Array);
161+
this.gl.uniform3fv(uLoc, value as Float32Array);
160162
break;
161163
case "vec4":
162-
this.gl?.uniform4fv(uLoc, value as Float32Array);
164+
this.gl.uniform4fv(uLoc, value as Float32Array);
163165
break;
164166
case "int":
165-
this.gl?.uniform1i(uLoc, value as number);
167+
this.gl.uniform1i(uLoc, value as number);
166168
break;
167169
case "ivec2":
168-
this.gl?.uniform2iv(uLoc, value as Int32Array);
170+
this.gl.uniform2iv(uLoc, value as Int32Array);
169171
break;
170172
case "ivec3":
171-
this.gl?.uniform3iv(uLoc, value as Int32Array);
173+
this.gl.uniform3iv(uLoc, value as Int32Array);
172174
break;
173175
case "ivec4":
174-
this.gl?.uniform4iv(uLoc, value as Int32Array);
176+
this.gl.uniform4iv(uLoc, value as Int32Array);
175177
break;
176178
case "mat2":
177-
this.gl?.uniformMatrix2fv(uLoc, false, value as Float32Array);
179+
this.gl.uniformMatrix2fv(uLoc, false, value as Float32Array);
178180
break;
179181
case "mat3":
180-
this.gl?.uniformMatrix3fv(uLoc, false, value as Float32Array);
182+
this.gl.uniformMatrix3fv(uLoc, false, value as Float32Array);
181183
break;
182184
case "mat4":
183-
this.gl?.uniformMatrix4fv(uLoc, false, value as Float32Array);
185+
this.gl.uniformMatrix4fv(uLoc, false, value as Float32Array);
184186
break;
185187
default:
186188
console.error(`Unsupported uniform type: ${type}`);
@@ -193,19 +195,24 @@ export class SimpleShaderCanvas extends domHandler {
193195
const { width, height } = this.container.getBoundingClientRect();
194196
this.container.width = width;
195197
this.container.height = height;
196-
this.gl?.viewport(0, 0, width, height);
198+
this.gl.viewport(0, 0, width, height);
199+
this.runHooks(MethodName.RESIZE);
197200

198201
this.render(); // Render immediately on resize to avoid jitter waiting for render call
199202
}
200203

201204
// Handles input events
202205
protected handleInput(e: Event): void {
203206
super.handleInput(e);
207+
this.runHooks(MethodName.INPUT);
208+
204209
}
205210

206211
// Handles touch start events
207212
protected touchStart(e: Event): void {
208213
super.touchStart(e);
209-
this.logic.touch?.(this);
214+
this.runHooks(MethodName.TOUCH);
215+
216+
210217
}
211-
}
218+
}

src/react/index.tsx

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,33 @@
11
'use client'
22

3-
import React, { useRef, useEffect } from "react";
4-
import { SimpleShaderCanvas as SimpleShaderCore } from "../core";
3+
import React, { useRef, useEffect, useState } from "react";
4+
import { Shader as SimpleShaderCore } from "../core";
55
import { shaderArgs } from "../types";
66

77
export const SimpleShaderCanvas = ({
8-
args,
9-
className,
10-
loadedClass = 'loaded'
11-
}: {
12-
args: shaderArgs,
13-
className?: string,
14-
loadedClass?: string
15-
}) => {
8+
args,
9+
className,
10+
loadedClass = 'loaded'
11+
}: {
12+
args: shaderArgs,
13+
className?: string,
14+
loadedClass?: string
15+
}) => {
1616

1717
const ref = useRef<HTMLCanvasElement>(null);
18+
19+
1820
useEffect(() => {
21+
1922
if (!ref.current) return
20-
23+
2124
const ShaderInstance = new SimpleShaderCore(ref.current, args);
2225
ShaderInstance.init()
2326

27+
2428
// return () => ShaderInstance. // TODO: ShaderInstance should be destroyed on return
2529
}, [args]);
26-
30+
2731
return (
2832
<canvas
2933
ref={ref}

src/types/index.d.ts

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,9 @@
1-
import { SimpleShaderCanvas } from "../core"
1+
import { Shader } from "../core"
2+
import { MethodName } from "../core/base";
23

34
declare namespace simpleShaderComponent {
4-
enum LogicProcesses {
5-
TOUCH = 'touch',
6-
INIT = 'init',
7-
LOOP = 'loop',
8-
}
9-
10-
/** Logic should be functions passed as strings that take the form (shader: Shader) => void */
11-
type LogicStrings = {
12-
[key in LogicProcesses]?: string; // Note string is later converted to Fn
13-
}
14-
15-
type LogicFns = {
16-
[key in LogicProcesses]?: ((shader: SimpleShaderCanvas) => void);
17-
}
185

6+
type ShaderHook = (shader: Shader, ...args: any[]) => void;
197

208
/**
219
* @param logic Logic should be functions passed as strings that take the form (shader: Shader) => void
@@ -24,7 +12,7 @@ declare namespace simpleShaderComponent {
2412
vertShader?: string,
2513
fragShader?: string,
2614
uniforms?: Array<UniformValue>,
27-
logic?: LogicStrings
15+
hooks?: Array<{ methodName: MethodName, hook: ShaderHook }>
2816
}
2917

3018
type UniformValue = {

0 commit comments

Comments
 (0)