summaryrefslogtreecommitdiff
path: root/apps/web/src/services/renderer/nodes/composite-effect-node.ts
blob: 3fbfffc196c74e83bde2b5142d8cbe8b58988a57 (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
74
75
76
77
import type { CanvasRenderer } from "../canvas-renderer";
import { createOffscreenCanvas } from "../canvas-utils";
import { getEffect } from "@/lib/effects";
import type { EffectParamValues } from "@/types/effects";
import { BaseNode } from "./base-node";
import { webglEffectRenderer } from "../webgl-effect-renderer";

export type CompositeEffectNodeParams = {
	contentNodes: BaseNode[];
	effectType: string;
	effectParams: EffectParamValues;
	scale: number;
};

export class CompositeEffectNode extends BaseNode<CompositeEffectNodeParams> {
	async render({
		renderer,
		time,
	}: {
		renderer: CanvasRenderer;
		time: number;
	}): Promise<void> {
		const offscreen = createOffscreenCanvas({
			width: renderer.width,
			height: renderer.height,
		});
		const offscreenCtx = offscreen.getContext("2d") as OffscreenCanvasRenderingContext2D | null;
		if (!offscreenCtx) {
			throw new Error("failed to get offscreen canvas context");
		}

		const originalContext = renderer.context;
		renderer.context = offscreenCtx;

		for (const node of this.params.contentNodes) {
			await node.render({ renderer, time });
		}

		renderer.context = originalContext;

		const effectDefinition = getEffect({ effectType: this.params.effectType });
		const scale = this.params.scale;
		const scaledWidth = renderer.width * scale;
		const scaledHeight = renderer.height * scale;
		const offsetX = (renderer.width - scaledWidth) / 2;
		const offsetY = (renderer.height - scaledHeight) / 2;

		const passes = effectDefinition.renderer.passes.map((pass) => ({
			fragmentShader: pass.fragmentShader,
			uniforms: pass.uniforms({
				effectParams: this.params.effectParams,
				width: renderer.width,
				height: renderer.height,
			}),
		}));
		const effectResult = webglEffectRenderer.applyEffect({
			source: offscreen as CanvasImageSource,
			width: renderer.width,
			height: renderer.height,
			passes,
		});

		renderer.context.save();
		renderer.context.drawImage(
			effectResult,
			0,
			0,
			renderer.width,
			renderer.height,
			offsetX,
			offsetY,
			scaledWidth,
			scaledHeight,
		);
		renderer.context.restore();
	}
}