OpenCut/apps/web/src/services/renderer/canvas-renderer.ts

112 lines
3.9 KiB
TypeScript

import type { FrameRate } from "opencut-wasm";
import type { AnyBaseNode } from "./nodes/base-node";
import { buildFrameDescriptor } from "./compositor/frame-descriptor";
import { wasmCompositor } from "./compositor/wasm-compositor";
import { resolveRenderTree } from "./resolve";
export type CanvasRendererParams = {
width: number;
height: number;
fps: FrameRate;
};
export class CanvasRenderer {
canvas: OffscreenCanvas | HTMLCanvasElement;
context: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;
width: number;
height: number;
fps: FrameRate;
constructor({ width, height, fps }: CanvasRendererParams) {
this.width = width;
this.height = height;
this.fps = fps;
try {
this.canvas = new OffscreenCanvas(width, height);
} catch {
this.canvas = document.createElement("canvas");
this.canvas.width = width;
this.canvas.height = height;
}
const context = this.canvas.getContext("2d");
if (!context) {
throw new Error("Failed to get canvas context");
}
this.context = context as
| OffscreenCanvasRenderingContext2D
| CanvasRenderingContext2D;
}
getOutputCanvas(): HTMLCanvasElement | null {
wasmCompositor.ensureInitialized({
width: this.width,
height: this.height,
});
return wasmCompositor.getCanvas();
}
setSize({ width, height }: { width: number; height: number }) {
this.width = width;
this.height = height;
if (this.canvas instanceof OffscreenCanvas) {
this.canvas = new OffscreenCanvas(width, height);
} else {
this.canvas.width = width;
this.canvas.height = height;
}
const context = this.canvas.getContext("2d");
if (!context) {
throw new Error("Failed to get canvas context");
}
this.context = context as
| OffscreenCanvasRenderingContext2D
| CanvasRenderingContext2D;
}
async render({ node, time }: { node: AnyBaseNode; time: number }) {
await resolveRenderTree({ node, renderer: this, time });
const { frame, textures } = await buildFrameDescriptor({
node,
renderer: this,
});
wasmCompositor.ensureInitialized({
width: this.width,
height: this.height,
});
wasmCompositor.syncTextures(textures);
wasmCompositor.render(frame);
}
async renderToCanvas({
node,
time,
targetCanvas,
}: {
node: AnyBaseNode;
time: number;
targetCanvas: HTMLCanvasElement;
}) {
await this.render({ node, time });
const ctx = targetCanvas.getContext("2d");
if (!ctx) {
throw new Error("Failed to get target canvas context");
}
const compositorCanvas = wasmCompositor.getCanvas();
if (!compositorCanvas) return;
ctx.drawImage(
compositorCanvas,
0,
0,
targetCanvas.width,
targetCanvas.height,
);
}
}