From e579ae120290329fd697b50ea9138ffccb65a805 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 1 Apr 2026 13:57:32 +0200 Subject: feat: migrate GPU renderer from WebGL to wgpu/WASM --- .../renderer/nodes/blur-background-node.ts | 4 +- .../services/renderer/nodes/effect-layer-node.ts | 162 ++++++++++----------- apps/web/src/services/renderer/nodes/text-node.ts | 4 +- .../web/src/services/renderer/nodes/visual-node.ts | 4 +- 4 files changed, 87 insertions(+), 87 deletions(-) (limited to 'apps/web/src/services/renderer/nodes') diff --git a/apps/web/src/services/renderer/nodes/blur-background-node.ts b/apps/web/src/services/renderer/nodes/blur-background-node.ts index 7f4d06fc..f790ebb4 100644 --- a/apps/web/src/services/renderer/nodes/blur-background-node.ts +++ b/apps/web/src/services/renderer/nodes/blur-background-node.ts @@ -5,7 +5,7 @@ import { videoCache } from "@/services/video-cache/service"; import type { RetimeConfig } from "@/lib/timeline"; import type { CanvasRenderer } from "../canvas-renderer"; import { createOffscreenCanvas } from "../canvas-utils"; -import { webglEffectRenderer } from "../webgl/webgl-effect-renderer"; +import { gpuRenderer } from "../gpu-renderer"; import { BaseNode } from "./base-node"; import { loadImageSource, type CachedImageSource } from "./image-node"; @@ -131,7 +131,7 @@ export class BlurBackgroundNode extends BaseNode { sigmaX: this.params.blurIntensity * (renderer.width / 1920), sigmaY: this.params.blurIntensity * (renderer.height / 1080), }); - const effectResult = webglEffectRenderer.applyEffect({ + const effectResult = gpuRenderer.applyEffect({ source: offscreen as CanvasImageSource, width: renderer.width, height: renderer.height, diff --git a/apps/web/src/services/renderer/nodes/effect-layer-node.ts b/apps/web/src/services/renderer/nodes/effect-layer-node.ts index 51de2398..d8389fb5 100644 --- a/apps/web/src/services/renderer/nodes/effect-layer-node.ts +++ b/apps/web/src/services/renderer/nodes/effect-layer-node.ts @@ -1,81 +1,81 @@ -import type { CanvasRenderer } from "../canvas-renderer"; -import { effectsRegistry, resolveEffectPasses } from "@/lib/effects"; -import type { ParamValues } from "@/lib/params"; -import { BaseNode } from "./base-node"; -import { webglEffectRenderer } from "../webgl/webgl-effect-renderer"; - -const TIME_EPSILON = 1e-6; - -export type EffectLayerNodeParams = { - effectType: string; - effectParams: ParamValues; - timeOffset: number; - duration: number; -}; - -function isInRange({ - time, - timeOffset, - duration, -}: { - time: number; - timeOffset: number; - duration: number; -}): boolean { - return ( - time >= timeOffset - TIME_EPSILON && - time < timeOffset + duration + TIME_EPSILON - ); -} - -// snapshots whatever is currently on the canvas, applies the effect, draws it back -export class EffectLayerNode extends BaseNode { - async render({ - renderer, - time, - }: { - renderer: CanvasRenderer; - time: number; - }): Promise { - if ( - !isInRange({ - time, - timeOffset: this.params.timeOffset, - duration: this.params.duration, - }) - ) { - return; - } - - const source = renderer.context.canvas as CanvasImageSource; - - const effectDefinition = effectsRegistry.get(this.params.effectType); - - const passes = resolveEffectPasses({ - definition: effectDefinition, - effectParams: this.params.effectParams, - width: renderer.width, - height: renderer.height, - }); - if (passes.length === 0) { - return; - } - const effectResult = webglEffectRenderer.applyEffect({ - source, - width: renderer.width, - height: renderer.height, - passes, - }); - - renderer.context.save(); - renderer.context.clearRect(0, 0, renderer.width, renderer.height); - renderer.context.drawImage( - effectResult, - 0, - 0, - renderer.width, - renderer.height, - ); - renderer.context.restore(); - } -} +import type { CanvasRenderer } from "../canvas-renderer"; +import { effectsRegistry, resolveEffectPasses } from "@/lib/effects"; +import type { ParamValues } from "@/lib/params"; +import { BaseNode } from "./base-node"; +import { gpuRenderer } from "../gpu-renderer"; + +const TIME_EPSILON = 1e-6; + +export type EffectLayerNodeParams = { + effectType: string; + effectParams: ParamValues; + timeOffset: number; + duration: number; +}; + +function isInRange({ + time, + timeOffset, + duration, +}: { + time: number; + timeOffset: number; + duration: number; +}): boolean { + return ( + time >= timeOffset - TIME_EPSILON && + time < timeOffset + duration + TIME_EPSILON + ); +} + +// snapshots whatever is currently on the canvas, applies the effect, draws it back +export class EffectLayerNode extends BaseNode { + async render({ + renderer, + time, + }: { + renderer: CanvasRenderer; + time: number; + }): Promise { + if ( + !isInRange({ + time, + timeOffset: this.params.timeOffset, + duration: this.params.duration, + }) + ) { + return; + } + + const source = renderer.context.canvas as CanvasImageSource; + + const effectDefinition = effectsRegistry.get(this.params.effectType); + + const passes = resolveEffectPasses({ + definition: effectDefinition, + effectParams: this.params.effectParams, + width: renderer.width, + height: renderer.height, + }); + if (passes.length === 0) { + return; + } + const effectResult = gpuRenderer.applyEffect({ + source, + width: renderer.width, + height: renderer.height, + passes, + }); + + renderer.context.save(); + renderer.context.clearRect(0, 0, renderer.width, renderer.height); + renderer.context.drawImage( + effectResult, + 0, + 0, + renderer.width, + renderer.height, + ); + renderer.context.restore(); + } +} diff --git a/apps/web/src/services/renderer/nodes/text-node.ts b/apps/web/src/services/renderer/nodes/text-node.ts index 031c43b3..643e538a 100644 --- a/apps/web/src/services/renderer/nodes/text-node.ts +++ b/apps/web/src/services/renderer/nodes/text-node.ts @@ -21,7 +21,7 @@ import { } from "@/lib/animation"; import { resolveEffectParamsAtTime } from "@/lib/animation/effect-param-channel"; import { effectsRegistry, resolveEffectPasses } from "@/lib/effects"; -import { webglEffectRenderer } from "../webgl/webgl-effect-renderer"; +import { gpuRenderer } from "../gpu-renderer"; import { clamp } from "@/utils/math"; const TEXT_DECORATION_THICKNESS_RATIO = 0.07; @@ -272,7 +272,7 @@ export class TextNode extends BaseNode { width: renderer.width, height: renderer.height, }); - currentSource = webglEffectRenderer.applyEffect({ + currentSource = gpuRenderer.applyEffect({ source: currentSource, width: renderer.width, height: renderer.height, diff --git a/apps/web/src/services/renderer/nodes/visual-node.ts b/apps/web/src/services/renderer/nodes/visual-node.ts index e0f87c52..82093da4 100644 --- a/apps/web/src/services/renderer/nodes/visual-node.ts +++ b/apps/web/src/services/renderer/nodes/visual-node.ts @@ -16,7 +16,7 @@ import { TIME_EPSILON_SECONDS } from "@/constants/animation-constants"; import { effectsRegistry, resolveEffectPasses } from "@/lib/effects"; import { masksRegistry } from "@/lib/masks"; import { getSourceTimeAtClipTime } from "@/lib/retime"; -import { webglEffectRenderer } from "../webgl/webgl-effect-renderer"; +import { gpuRenderer } from "../gpu-renderer"; import { applyMaskFeather } from "../mask-feather"; export interface VisualNodeParams { @@ -205,7 +205,7 @@ export abstract class VisualNode< width, height, }); - current = webglEffectRenderer.applyEffect({ + current = gpuRenderer.applyEffect({ source: current, width: Math.round(width), height: Math.round(height), -- cgit v1.3.1