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

7 public exports · 7 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. In its home contexts (Node: server-render, conformance, tests) it is always available and never returns `backend-unavailable`. The ONE exception is the browser-cpu-exclusion guard: constructed with a live DOM (`window` + `document` present) it fires a `warnUnexpected` beacon and returns a typed refusal that computes no pixels — the browser is GPU-only (gpu-preview-pipeline decision 2026-07-10). 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.

probeBrowserContextfunction
probeBrowserContext(): BrowserContextProbe

Probe `globalThis` for a browser context: BOTH `window` and `document` present (per the browser-cpu-exclusion spec). Probing `globalThis` properties (not the bare `window` identifier) avoids a ReferenceError on hosts where the global does not exist.

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.

BrowserContextProbetype
type BrowserContextProbe

Outcome of probing the host for a live browser DOM. Never a bare boolean.

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.

CPU_BACKEND_SENTINELconst
CPU_BACKEND_SENTINEL: "faceless-photolib.cpu-executor.sentinel.9f4c1b7e2a63"

The unique CPU-executor bundle sentinel (browser-cpu-exclusion spec). The browser is GPU-only: this package must never be delivered to a client bundle. This constant is the marker the post-build check greps for in the site's client chunks (`scripts/check-client-bundle.mjs`): if the literal appears in any client-delivered chunk, `backend-cpu` executor code leaked into the browser build and the check fails naming the chunk. Anti-rot: the same script also verifies this export still exists in this file — removing or renaming it makes the bundle check fail with a "sentinel missing" error instead of passing vacuously. The constant is referenced from `createBackend`'s browser-refusal path (a code path that is always retained when the executor entry is bundled), so a minifier cannot tree-shake the literal away while keeping the executor.