summaryrefslogtreecommitdiff
path: root/apps/web/src/services/renderer/webgl-effect-renderer.ts
blob: ad5cd40e9c0e114a1f94cb90fa1e4343f29ac49a (plain) (blame)
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,
};