@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-cpuUsage
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
| Export | Description |
|---|---|
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. |
CpuBackend | The backend interface — a structural superset of GpuBackend adding renderAsync. Has render, renderAsync, info, dispose, and kind: "cpu". |
SourceImage | A 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.
createBackendfunctioncreateBackend(resolveSource?: SourceResolver | undefined, textRasterizer?: TextRasterizer | undefined): CpuBackendCPU 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.
CpuBackendinterfaceinterface CpuBackendThe 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.
SourceImageinterfaceinterface SourceImageA 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.
SourceResolvertypetype SourceResolverResolve 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.
@faceless-photolib/render-graph
Compiles a faceless-photolib document layer stack into a color-managed, backend-agnostic render graph of GPU passes, with the GpuBackend and TextRasterizer port definitions.
@faceless-photolib/backend-webgpu
WebGPU backend for faceless-photolib — WGSL codegen and pipeline/bind-group descriptor construction from the render-graph IR, with one shading-language codebase for browser, Node (Dawn), and Expo/RN.