mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
feat: migrate GPU renderer from WebGL to wgpu/WASM
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
import {
|
||||
applyEffectPasses,
|
||||
applyMaskFeather as applyMaskFeatherWasm,
|
||||
initializeGpu,
|
||||
} from "opencut-wasm";
|
||||
import type { EffectPass, EffectUniformValue } from "@/lib/effects/types";
|
||||
|
||||
let initializeGpuRendererPromise: Promise<void> | null = null;
|
||||
|
||||
export function initializeGpuRenderer(): Promise<void> {
|
||||
if (!initializeGpuRendererPromise) {
|
||||
initializeGpuRendererPromise = initializeGpu();
|
||||
}
|
||||
const promise = initializeGpuRendererPromise;
|
||||
if (!promise) {
|
||||
throw new Error("GPU renderer initialization promise was not created");
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
export const gpuRenderer = {
|
||||
applyEffect({
|
||||
source,
|
||||
width,
|
||||
height,
|
||||
passes,
|
||||
}: {
|
||||
source: CanvasImageSource;
|
||||
width: number;
|
||||
height: number;
|
||||
passes: EffectPass[];
|
||||
}): CanvasImageSource {
|
||||
if (passes.length === 0) {
|
||||
return source;
|
||||
}
|
||||
|
||||
const sourceCanvas = ensureOffscreenCanvas({
|
||||
source,
|
||||
width,
|
||||
height,
|
||||
label: "effect source",
|
||||
});
|
||||
return applyEffectPasses({
|
||||
source: sourceCanvas,
|
||||
width,
|
||||
height,
|
||||
passes: serializeEffectPasses(passes),
|
||||
});
|
||||
},
|
||||
|
||||
applyMaskFeather({
|
||||
maskCanvas,
|
||||
width,
|
||||
height,
|
||||
feather,
|
||||
}: {
|
||||
maskCanvas: CanvasImageSource;
|
||||
width: number;
|
||||
height: number;
|
||||
feather: number;
|
||||
}): CanvasImageSource {
|
||||
const sourceCanvas = ensureOffscreenCanvas({
|
||||
source: maskCanvas,
|
||||
width,
|
||||
height,
|
||||
label: "mask source",
|
||||
});
|
||||
return applyMaskFeatherWasm({
|
||||
mask: sourceCanvas,
|
||||
width,
|
||||
height,
|
||||
feather,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
function ensureOffscreenCanvas({
|
||||
source,
|
||||
width,
|
||||
height,
|
||||
label,
|
||||
}: {
|
||||
source: CanvasImageSource;
|
||||
width: number;
|
||||
height: number;
|
||||
label: string;
|
||||
}): OffscreenCanvas {
|
||||
if (source instanceof OffscreenCanvas) {
|
||||
return source;
|
||||
}
|
||||
|
||||
if (typeof OffscreenCanvas === "undefined") {
|
||||
throw new Error(`OffscreenCanvas is required for the GPU ${label}`);
|
||||
}
|
||||
|
||||
const canvas = new OffscreenCanvas(width, height);
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) {
|
||||
throw new Error(`Failed to get 2d context for the GPU ${label}`);
|
||||
}
|
||||
context.clearRect(0, 0, width, height);
|
||||
context.drawImage(source, 0, 0, width, height);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function serializeEffectPasses(passes: EffectPass[]) {
|
||||
return passes.map((pass) => ({
|
||||
shader: pass.shader,
|
||||
uniforms: Object.entries(pass.uniforms).map(([name, value]) => ({
|
||||
name,
|
||||
value: normalizeUniformValue(value),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
function normalizeUniformValue(value: EffectUniformValue): number[] {
|
||||
return typeof value === "number" ? [value] : value;
|
||||
}
|
||||
Reference in New Issue
Block a user