1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
import { createOffscreenCanvas } from "./canvas-utils";
import { applyMultiPassEffect } from "./webgl-utils";
import type { EffectPassData } from "./webgl-utils";
export interface ApplyEffectParams {
source: CanvasImageSource;
width: number;
height: number;
passes: EffectPassData[];
}
let gl: WebGLRenderingContext | null = null;
let canvas: OffscreenCanvas | HTMLCanvasElement | null = null;
const programCache = new Map<string, WebGLProgram>();
function getOrCreateCanvas({
width,
height,
}: {
width: number;
height: number;
}): OffscreenCanvas | HTMLCanvasElement {
if (!canvas) {
canvas = createOffscreenCanvas({ width, height });
gl = canvas.getContext("webgl", {
premultipliedAlpha: false,
}) as WebGLRenderingContext | null;
if (!gl) {
throw new Error("WebGL not supported");
}
}
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
return canvas;
}
function applyEffect({
source,
width,
height,
passes,
}: ApplyEffectParams): OffscreenCanvas | HTMLCanvasElement {
const targetCanvas = getOrCreateCanvas({ width, height });
const context = gl;
if (!context) {
throw new Error("WebGL context not initialized");
}
applyMultiPassEffect({
context,
source,
width,
height,
passes,
programCache,
});
const outputCanvas = createOffscreenCanvas({ width, height });
const outputCtx = outputCanvas.getContext("2d") as
| CanvasRenderingContext2D
| OffscreenCanvasRenderingContext2D
| null;
if (outputCtx) {
outputCtx.drawImage(targetCanvas, 0, 0, width, height);
}
return outputCanvas;
}
export const webglEffectRenderer = {
applyEffect,
};
|