summaryrefslogtreecommitdiff
path: root/apps/web/src/services/renderer
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-29 00:37:56 +0200
committerMaze Winther <mazewinther@gmail.com>2026-05-02 03:27:23 +0200
commita9b9cf6bf50f08723896659634f2e6afc17bcd42 (patch)
tree9175b49ff21f4326872ac9e541d2765cc7994ebc /apps/web/src/services/renderer
parentd6622dc6b3523a5658cd512b44f15f7a66fd540c (diff)
downloadopencut-a9b9cf6bf50f08723896659634f2e6afc17bcd42.tar.gz
chore: switch from biome to eslint + prettier; fix ton of lint issues
Diffstat (limited to 'apps/web/src/services/renderer')
-rw-r--r--apps/web/src/services/renderer/canvas-renderer.ts41
-rw-r--r--apps/web/src/services/renderer/canvas-utils.ts19
-rw-r--r--apps/web/src/services/renderer/compositor/frame-descriptor.ts23
-rw-r--r--apps/web/src/services/renderer/effect-preview.ts25
-rw-r--r--apps/web/src/services/renderer/gpu-renderer.ts53
-rw-r--r--apps/web/src/services/renderer/mask-feather.ts6
-rw-r--r--apps/web/src/services/renderer/nodes/graphic-node.ts17
-rw-r--r--apps/web/src/services/renderer/nodes/image-node.ts11
-rw-r--r--apps/web/src/services/renderer/resolve.ts10
9 files changed, 61 insertions, 144 deletions
diff --git a/apps/web/src/services/renderer/canvas-renderer.ts b/apps/web/src/services/renderer/canvas-renderer.ts
index 4a002675..58294e9c 100644
--- a/apps/web/src/services/renderer/canvas-renderer.ts
+++ b/apps/web/src/services/renderer/canvas-renderer.ts
@@ -1,5 +1,6 @@
import type { FrameRate } from "opencut-wasm";
import type { AnyBaseNode } from "./nodes/base-node";
+import { createCanvasSurface } from "./canvas-utils";
import { buildFrameDescriptor } from "./compositor/frame-descriptor";
import { wasmCompositor } from "./compositor/wasm-compositor";
import { resolveRenderTree } from "./resolve";
@@ -16,8 +17,8 @@ export type CanvasRendererParams = {
};
export class CanvasRenderer {
- canvas: OffscreenCanvas | HTMLCanvasElement;
- context: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;
+ canvas: OffscreenCanvas;
+ context: OffscreenCanvasRenderingContext2D;
width: number;
height: number;
fps: FrameRate;
@@ -27,22 +28,9 @@ export class CanvasRenderer {
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;
+ const surface = createCanvasSurface({ width, height });
+ this.canvas = surface.canvas;
+ this.context = surface.context;
}
getOutputCanvas(): HTMLCanvasElement {
@@ -57,20 +45,9 @@ export class CanvasRenderer {
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;
+ const surface = createCanvasSurface({ width, height });
+ this.canvas = surface.canvas;
+ this.context = surface.context;
}
async render({ node, time }: { node: AnyBaseNode; time: number }) {
diff --git a/apps/web/src/services/renderer/canvas-utils.ts b/apps/web/src/services/renderer/canvas-utils.ts
index 093168e6..6981e6d5 100644
--- a/apps/web/src/services/renderer/canvas-utils.ts
+++ b/apps/web/src/services/renderer/canvas-utils.ts
@@ -1,16 +1,17 @@
-export function createOffscreenCanvas({
+export function createCanvasSurface({
width,
height,
}: {
width: number;
height: number;
-}): OffscreenCanvas | HTMLCanvasElement {
- try {
- return new OffscreenCanvas(width, height);
- } catch {
- const canvas = document.createElement("canvas");
- canvas.width = width;
- canvas.height = height;
- return canvas;
+}): {
+ canvas: OffscreenCanvas;
+ context: OffscreenCanvasRenderingContext2D;
+} {
+ const canvas = new OffscreenCanvas(width, height);
+ const context = canvas.getContext("2d");
+ if (!context) {
+ throw new Error("Failed to create 2D rendering context");
}
+ return { canvas, context };
}
diff --git a/apps/web/src/services/renderer/compositor/frame-descriptor.ts b/apps/web/src/services/renderer/compositor/frame-descriptor.ts
index 190a0237..1bc316ef 100644
--- a/apps/web/src/services/renderer/compositor/frame-descriptor.ts
+++ b/apps/web/src/services/renderer/compositor/frame-descriptor.ts
@@ -3,7 +3,7 @@ import { masksRegistry } from "@/masks";
import { incrementCounter } from "@/diagnostics/render-perf";
import type { AnyBaseNode } from "../nodes/base-node";
import type { CanvasRenderer } from "../canvas-renderer";
-import { createOffscreenCanvas } from "../canvas-utils";
+import { createCanvasSurface } from "../canvas-utils";
import { BlurBackgroundNode } from "../nodes/blur-background-node";
import { ColorNode } from "../nodes/color-node";
import { EffectLayerNode } from "../nodes/effect-layer-node";
@@ -414,15 +414,11 @@ function buildMaskArtifacts({
const { width: canvasWidth, height: canvasHeight } = renderer;
const maskContentHash = `mask:${mask.type}:${JSON.stringify(mask.params)}:${transformHash(transform)}:${canvasWidth}x${canvasHeight}:direct=${shouldRenderMaskDirectly}`;
const drawMask: TextureCanvasDrawFn = (ctx) => {
- const elementMaskCanvas = createOffscreenCanvas({
- width: Math.round(transform.width),
- height: Math.round(transform.height),
- });
- const elementMaskCtx = elementMaskCanvas.getContext("2d") as
- | CanvasRenderingContext2D
- | OffscreenCanvasRenderingContext2D
- | null;
- if (!elementMaskCtx) return;
+ const { canvas: elementMaskCanvas, context: elementMaskCtx } =
+ createCanvasSurface({
+ width: Math.round(transform.width),
+ height: Math.round(transform.height),
+ });
if (shouldRenderMaskDirectly && definition.renderer.renderMask) {
definition.renderer.renderMask({
@@ -465,15 +461,10 @@ function buildMaskArtifacts({
const strokeTextureId = `${path}:mask-stroke`;
const strokeContentHash = `stroke:${mask.type}:${JSON.stringify(mask.params)}:${transformHash(transform)}:${canvasWidth}x${canvasHeight}`;
const drawStroke: TextureCanvasDrawFn = (ctx) => {
- const strokeCanvas = createOffscreenCanvas({
+ const { canvas: strokeCanvas, context: strokeCtx } = createCanvasSurface({
width: Math.round(transform.width),
height: Math.round(transform.height),
});
- const strokeCtx = strokeCanvas.getContext("2d") as
- | CanvasRenderingContext2D
- | OffscreenCanvasRenderingContext2D
- | null;
- if (!strokeCtx) return;
if (definition.renderer.renderStroke) {
definition.renderer.renderStroke({
diff --git a/apps/web/src/services/renderer/effect-preview.ts b/apps/web/src/services/renderer/effect-preview.ts
index c4cacde0..cbfb1a31 100644
--- a/apps/web/src/services/renderer/effect-preview.ts
+++ b/apps/web/src/services/renderer/effect-preview.ts
@@ -1,4 +1,4 @@
-import { createOffscreenCanvas } from "./canvas-utils";
+import { createCanvasSurface } from "./canvas-utils";
import { effectsRegistry, resolveEffectPasses } from "@/effects";
import { buildDefaultParamValues } from "@/params/registry";
import type { ParamValues } from "@/params";
@@ -8,7 +8,7 @@ const PREVIEW_SIZE = 160;
const PREVIEW_IMAGE_PATH = "/effects/preview.jpg";
class EffectPreviewService {
- private testSourceCanvas: OffscreenCanvas | HTMLCanvasElement | null = null;
+ private testSourceCanvas: OffscreenCanvas | null = null;
private previewImageElement: HTMLImageElement | null = null;
private onReadyCallbacks = new Set<() => void>();
@@ -98,7 +98,7 @@ class EffectPreviewService {
}: {
width: number;
height: number;
- }): OffscreenCanvas | HTMLCanvasElement | null {
+ }): OffscreenCanvas | null {
const isImageReady =
this.previewImageElement?.complete &&
(this.previewImageElement.naturalWidth ?? 0) > 0;
@@ -106,15 +106,8 @@ class EffectPreviewService {
return null;
}
- const canvas = createOffscreenCanvas({ width, height });
- const ctx = canvas.getContext("2d") as
- | CanvasRenderingContext2D
- | OffscreenCanvasRenderingContext2D
- | null;
- if (!ctx) {
- throw new Error("failed to get 2d context for test source");
- }
- ctx.drawImage(this.previewImageElement, 0, 0, width, height);
+ const { canvas, context } = createCanvasSurface({ width, height });
+ context.drawImage(this.previewImageElement, 0, 0, width, height);
return canvas;
}
@@ -124,7 +117,7 @@ class EffectPreviewService {
}: {
width: number;
height: number;
- }): CanvasImageSource | null {
+ }): OffscreenCanvas | null {
if (
!this.testSourceCanvas ||
this.testSourceCanvas.width !== width ||
@@ -141,17 +134,17 @@ class EffectPreviewService {
height,
passes,
}: {
- source: CanvasImageSource;
+ source: OffscreenCanvas;
width: number;
height: number;
passes: ReturnType<typeof resolveEffectPasses>;
- }): OffscreenCanvas | HTMLCanvasElement {
+ }): OffscreenCanvas {
return gpuRenderer.applyEffect({
source,
width,
height,
passes,
- }) as OffscreenCanvas | HTMLCanvasElement;
+ });
}
}
diff --git a/apps/web/src/services/renderer/gpu-renderer.ts b/apps/web/src/services/renderer/gpu-renderer.ts
index c61b6b98..c1be3f1a 100644
--- a/apps/web/src/services/renderer/gpu-renderer.ts
+++ b/apps/web/src/services/renderer/gpu-renderer.ts
@@ -34,23 +34,17 @@ export const gpuRenderer = {
height,
passes,
}: {
- source: CanvasImageSource;
+ source: OffscreenCanvas;
width: number;
height: number;
passes: EffectPass[];
- }): CanvasImageSource {
+ }): OffscreenCanvas {
if (passes.length === 0 || !gpuAvailable) {
return source;
}
- const sourceCanvas = ensureOffscreenCanvas({
- source,
- width,
- height,
- label: "effect source",
- });
return applyEffectPasses({
- source: sourceCanvas,
+ source,
width,
height,
passes: serializeEffectPasses(passes),
@@ -63,23 +57,17 @@ export const gpuRenderer = {
height,
feather,
}: {
- maskCanvas: CanvasImageSource;
+ maskCanvas: OffscreenCanvas;
width: number;
height: number;
feather: number;
- }): CanvasImageSource {
+ }): OffscreenCanvas {
if (!gpuAvailable) {
return maskCanvas;
}
- const sourceCanvas = ensureOffscreenCanvas({
- source: maskCanvas,
- width,
- height,
- label: "mask source",
- });
return applyMaskFeatherWasm({
- mask: sourceCanvas,
+ mask: maskCanvas,
width,
height,
feather,
@@ -87,35 +75,6 @@ export const gpuRenderer = {
},
};
-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,
diff --git a/apps/web/src/services/renderer/mask-feather.ts b/apps/web/src/services/renderer/mask-feather.ts
index 4ea7eb2b..dde4decc 100644
--- a/apps/web/src/services/renderer/mask-feather.ts
+++ b/apps/web/src/services/renderer/mask-feather.ts
@@ -6,15 +6,15 @@ export function applyMaskFeather({
height,
feather,
}: {
- maskCanvas: CanvasImageSource;
+ maskCanvas: OffscreenCanvas;
width: number;
height: number;
feather: number;
-}): OffscreenCanvas | HTMLCanvasElement {
+}): OffscreenCanvas {
return gpuRenderer.applyMaskFeather({
maskCanvas,
width,
height,
feather,
- }) as OffscreenCanvas | HTMLCanvasElement;
+ });
}
diff --git a/apps/web/src/services/renderer/nodes/graphic-node.ts b/apps/web/src/services/renderer/nodes/graphic-node.ts
index 2d022510..798b56a0 100644
--- a/apps/web/src/services/renderer/nodes/graphic-node.ts
+++ b/apps/web/src/services/renderer/nodes/graphic-node.ts
@@ -1,4 +1,4 @@
-import { createOffscreenCanvas } from "../canvas-utils";
+import { createCanvasSurface } from "../canvas-utils";
import {
DEFAULT_GRAPHIC_SOURCE_SIZE,
getGraphicDefinition,
@@ -25,7 +25,7 @@ export class GraphicNode extends VisualNode<
ResolvedGraphicNodeState
> {
private cachedKey: string | null = null;
- private cachedSource: OffscreenCanvas | HTMLCanvasElement | null = null;
+ private cachedSource: OffscreenCanvas | null = null;
constructor(params: GraphicNodeParams) {
super(params);
@@ -36,7 +36,7 @@ export class GraphicNode extends VisualNode<
resolvedParams,
}: {
resolvedParams: ParamValues;
- }): OffscreenCanvas | HTMLCanvasElement | null {
+ }): OffscreenCanvas {
const definition = getGraphicDefinition({
definitionId: this.params.definitionId,
});
@@ -48,20 +48,13 @@ export class GraphicNode extends VisualNode<
return this.cachedSource;
}
- const canvas = createOffscreenCanvas({
+ const { canvas, context } = createCanvasSurface({
width: DEFAULT_GRAPHIC_SOURCE_SIZE,
height: DEFAULT_GRAPHIC_SOURCE_SIZE,
});
- const ctx = canvas.getContext("2d") as
- | CanvasRenderingContext2D
- | OffscreenCanvasRenderingContext2D
- | null;
- if (!ctx) {
- return null;
- }
definition.render({
- ctx,
+ ctx: context,
params: resolvedParams,
width: DEFAULT_GRAPHIC_SOURCE_SIZE,
height: DEFAULT_GRAPHIC_SOURCE_SIZE,
diff --git a/apps/web/src/services/renderer/nodes/image-node.ts b/apps/web/src/services/renderer/nodes/image-node.ts
index d8c56803..4f475a2d 100644
--- a/apps/web/src/services/renderer/nodes/image-node.ts
+++ b/apps/web/src/services/renderer/nodes/image-node.ts
@@ -17,10 +17,13 @@ export interface CachedImageSource {
const imageSourceCache = new Map<string, Promise<CachedImageSource>>();
-export function loadImageSource(
- url: string,
- maxSourceSize?: number,
-): Promise<CachedImageSource> {
+export function loadImageSource({
+ url,
+ maxSourceSize,
+}: {
+ url: string;
+ maxSourceSize?: number;
+}): Promise<CachedImageSource> {
const cacheKey = `${url}::${maxSourceSize ?? "full"}`;
const cached = imageSourceCache.get(cacheKey);
diff --git a/apps/web/src/services/renderer/resolve.ts b/apps/web/src/services/renderer/resolve.ts
index 36a6ecb5..4637df8d 100644
--- a/apps/web/src/services/renderer/resolve.ts
+++ b/apps/web/src/services/renderer/resolve.ts
@@ -235,10 +235,10 @@ async function resolveImageNode({
node: ImageNode;
context: ResolveContext;
}): Promise<ResolvedVisualSourceNodeState | null> {
- const source = await loadImageSource(
- node.params.url,
- node.params.maxSourceSize,
- );
+ const source = await loadImageSource({
+ url: node.params.url,
+ maxSourceSize: node.params.maxSourceSize,
+ });
const visualState = resolveVisualState({
params: node.params,
context,
@@ -434,7 +434,7 @@ async function resolveBackdropSource({
};
}
- const source = await loadImageSource(node.params.url);
+ const source = await loadImageSource({ url: node.params.url });
return {
source: source.source,
width: source.width,