faceless-photolib

@faceless-photolib/backend-cpu

The CPU reference backend for faceless-photolib — executes a compiled render graph in pure float TypeScript, the golden renderer other backends are measured against.

The CPU reference backend for faceless-photolib — a headless, color-managed, GPU-accelerated image-editing engine.

This package executes a compiled render graph in pure float TypeScript — no device, no approximations. It is the golden renderer the GPU and other backends are measured against, so it is always available and never reports backend-unavailable. Non-text compute is synchronous and resolves a terminal Resource; text rasterization (the one async, platform-dependent node) is handled by an injected port through the additive renderAsync path.

Install

pnpm add @faceless-photolib/backend-cpu

Usage

import { createBackend, type SourceImage, type SourceResolver } from "@faceless-photolib/backend-cpu";
import { match } from "ts-pattern";

// Inject a resolver that turns a `source` pass `assetHash` into decoded pixels:
// straight (un-premultiplied) f32 RGBA, row-major, length = width * height * 4.
const resolveSource: SourceResolver = (assetHash) => {
  const image: SourceImage = { width, height, pixels };
  return { kind: "ok", value: image };
};

const backend = createBackend(resolveSource);

// Synchronous golden render (no text passes).
const result = backend.render(compiledGraph);

match(result)
  .with({ status: "ready" }, ({ value }) => useRender(value))
  .with({ status: "error" }, ({ message }) => reportError(message))
  .otherwise(() => {});

// Documents with `text` passes need the async path (and a TextRasterizer at construction).
const withText = await backend.renderAsync(compiledGraph);

backend.dispose();

With no resolver injected, a source/mask pass fails loud (error) rather than fabricating pixels; with no text rasterizer, a text pass fails loud not-implemented.

API

ExportDescription
createBackend(resolveSource?, textRasterizer?)Constructs a CpuBackend. The optional SourceResolver decodes source/mask asset hashes; the optional TextRasterizer enables the async text path. Both injected at construction so the zero-arg createBackend() call site stays source-compatible.
CpuBackendThe backend interface — a structural superset of GpuBackend adding renderAsync. Has render, renderAsync, info, dispose, and kind: "cpu".
SourceImageA decoded source image: width, height, and straight (un-premultiplied) f32 RGBA pixels (row-major, length = width * height * 4).
SourceResolver(assetHash: string) => Result<SourceImage> — resolves a source pass's asset hash; a hash it cannot supply is a canonical failure, never fabricated pixels.

render is the frozen synchronous GpuBackend.render (no text); renderAsync awaits the injected TextRasterizer for any text pass and resolves identically to render for graphs with none. Text is OUTSIDE the cross-backend parity guarantee.

License

MIT

API reference

4 public exports · 4 documented · generated from source.

createBackendfunction
createBackend(resolveSource?: SourceResolver | undefined, textRasterizer?: TextRasterizer | undefined): CpuBackend

CPU reference backend (gpu-backend spec; D12). A plain float TypeScript implementation of every render-graph node — the GOLDEN source other backends are measured against. It is always available and MUST NEVER return `backend-unavailable`. Non-text compute is synchronous (pure float math, no device), so `render` resolves the `Resource` to `ready` (or `error`) immediately; text is rasterized by an injected per-platform port via the async `renderAsync` path (text is OUTSIDE the cross-backend parity guarantee). The frozen `GpuBackend` port carries no asset resolver / text rasterizer, yet a `source`/`mask` pass holds only an `assetHash` and a `text` pass holds only a spec. Mirroring how `resolveLut` is "injected at the runtime layer", the CPU backend takes both at construction (optional, so the frozen zero-arg `createBackend()` call sites stay source-compatible). With no resolver a graph with a `source`/`mask` pass fails loud (`error`); with no rasterizer a `text` pass fails loud `not-implemented` — never fabricated/blank pixels.

CpuBackendinterface
interface CpuBackend

The CPU reference backend, plus the additive async render path text needs. `render` is the frozen `GpuBackend.render` (synchronous, no text); `renderAsync` rasterizes any `text` passes through the injected `TextRasterizer` first (text is the one async node). `CpuBackend` is a structural superset of `GpuBackend`, so it satisfies the frozen port unchanged.

SourceImageinterface
interface SourceImage

A decoded source image as the CPU backend consumes it: straight (un-premultiplied) f32 RGBA, row-major, length = `width * height * 4`. The pixels are in the source layer's *declared* color space (the value the compiler's `source` pass emits) — the graph's auto-inserted `colorConversion` pass then brings them into the ACEScg connection space. Decoding bytes into this shape is the codec layer's job, not the golden-math reference's, so this is the minimal hand-off the executor needs.

SourceResolvertype
type SourceResolver

Resolve a `source` render-pass `assetHash` into its decoded pixels. The frozen `render(graph)` port carries no resolver, so — exactly as `resolveLut` is "injected at the runtime layer" — the CPU backend takes this resolver at construction. A `source` pass whose hash the resolver cannot supply is a canonical failure (`not-found` / `rejected`), never fabricated pixels.

On this page