API reference

The complete public API — Denoiser.create options, denoise/denoiseToFloat/denoiseTextures, properties, events, error classes, and the lower-level exports.

Handwritten from source (packages/denoiser/src/{types,denoiser,index}.ts). Every default below is verified against the shipped implementation, not the type comments alone.

import { Denoiser } from 'denoiser';

Denoiser

Construction is async-only — the constructor is private. Denoiser.create() loads the default (color-only) model and creates the shared GPUDevice before resolving, so denoiser.device is available immediately after await. The default runtime is AutoRuntime: WebNN fp16 for base/large models when WebNN is available, the hand-written WGSL runtime otherwise. It never loads onnxruntime-web.

Denoiser.create(options?)

static async create(options?: DenoiserCreateOptions): Promise<Denoiser>

DenoiserCreateOptions

optiontypedefaultmeaning
precision'fp32' | 'fp16''fp32'fp16 models + tensors + WGSL IO when the device's shader-f16 feature is available. Denoiser.create auto-falls-back to fp32 (with a console warning) if session creation fails under fp16
qualityQuality ('fast' | 'balanced' | 'high')'fast'model family: fast = *_small, balanced = base, high = *_large where that size exists for the variant. Mutable after creation via denoiser.quality
tzaUrlstringjsDelivr: https://cdn.jsdelivr.net/gh/pmndrs/denoiser-weights@models-v3/tzasbase URL the OIDN .tza weight files are fetched from by the default runtime. Ignored when runtime is passed
deviceGPUDevicea max-limits device the runtime createsrun the default runtime on this existing device, for example a three.js renderer's (renderer.backend.device). Ignored when runtime is passed. See The GPUDevice
maxRunPixelsnumber2048 * 1152 (2 359 296)per-run pixel budget (padded W×H); above it the engine tiles instead of running whole-frame. See Large images & tiling
batchnumber8max tiles batched into one session.run in tiled mode
runtimeNetworkRuntimeAutoRuntime (built from tzaUrl and device)what executes the network — see Network runtimes and Choosing a runtime. When passed, tzaUrl / device are ignored (configure the runtime itself). The ORT-only options (weightsUrl, wasmPaths, graphCapture, splitAux) are no longer Denoiser.create options; they belong to new OrtRuntime({...}) from denoiser/ort

Properties

membertypenotes
deviceGPUDevicethe shared device: the device option if you passed one, otherwise one the runtime created. Share it with your renderer — build your own pipelines on it, don't create your own device. See Custom engines
statsDenoiseStats | undefined (getter)per-stage wall-clock timings from the most recently completed run; undefined before the first call. See DenoiseStats below
modelNamestring | undefined (getter)the model file the last call actually ran (e.g. rt_hdr_calb_cnrm); undefined before the first call. Changes automatically when different inputs (hdr/aux) select a different variant
qualityQuality (getter/setter)current quality tier; assign to switch — the next call rebuilds the engine for the new model if needed

denoise(color, options?)

async denoise(color: ImgInput, options?: DenoiseImageOptions): Promise<ImageData | undefined>

Denoises an image-like input on the CPU-array path. Returns undefined only if abort() was called before the run resolved.

DenoiseImageOptions

optiontypedefaultmeaning
albedoImgInput–aux plane; bytes read as [0,1] linear (never sRGB-decoded — only color honors srgb). Must match color's size or throws DenoiserInputError
normalImgInput–aux plane; RGBA8 bytes already encoding [-1,1] as [0,1] (n*0.5+0.5); env/miss pixels should be 0 (→ encodes to mid-gray 0.5). Must match color's size
srgbbooleantruecolor input is sRGB-encoded (photos/screens) — decoded to linear for the model, re-encoded on the way out. Set false if your bytes are already linear
flipYbooleanfalseflip the result vertically
onProgress(p: number) => void–see progress semantics

denoiseToFloat(color, options?)

async denoiseToFloat(color: ImgInput, options?: DenoiseImageOptions): Promise<Float32Array | undefined>

Same as denoise(), but returns normalized [0,1] RGBA floats (ImageData bytes ÷ 255) instead of ImageData. Same options, same undefined-on-abort behavior. Internally calls denoise(), so it pays the same 8-bit quantization — use denoiseTextures if you need real float/HDR fidelity.

denoiseTextures(options)

async denoiseTextures(options: DenoiseTexturesOptions): Promise<GPUTexture | undefined>

Zero-copy: float GPUTextures in, GPUTexture out, no CPU round-trip. Returns undefined only if abort() was called before the run resolved.

DenoiseTexturesOptions

optiontypedefaultmeaning
colorGPUTexturerequirednoisy beauty input — float format (rgba16float/rgba32float), always treated as linear (no sRGB decode on the texture path)
albedoGPUTexture–aux plane, [0,1] floats
normalGPUTexture–aux plane, [-1,1] floats — the network's [0,1] encoding is applied internally
hdrbooleanfalseapply OIDN's PU transfer + autoexposure around the model
inputScalenumberauto (autoexposure)manual HDR exposure scale; set to skip the GPU autoexposure pass
inputFlipYbooleanfalsecolor rows are bottom-up (compute-written WebGPU targets)
auxInputFlipYboolean= inputFlipYindependent flip for albedo/normal when their convention differs from color
outputGPUTexture–caller-owned destination. Must match color's width/height, have STORAGE_BINDING usage, and be rgba8unorm or rgba16float — else throws. Omit to get an engine-owned texture back instead
transferOutputTransfer ('linear' | 'srgb' | 'aces-srgb')'linear'output encoding: 'linear' = raw model output (HDR-safe), 'srgb' = sRGB-encode, 'aces-srgb' = ACES tonemap + sRGB-encode (display-ready)
outputFlipYbooleanfalseflip the result vertically
onProgress(p: number) => void–see progress semantics

Returns your output texture if one was given; otherwise an engine-owned rgba8unorm texture valid only until the next call / a size change / dispose()/destroyDevice() — copy or sample it before calling again.

abort()

abort(): void

Marks the in-flight run so it resolves undefined instead of a result. Does not cancel GPU work already submitted — the work still completes, its result is just dropped. Typical use: drop a stale in-progress denoise when the camera/scene changed mid-run.

dispose()

dispose(): void

Frees per-image GPU buffers and all but the default (first-created) model session/geometry. Keeps the shared device alive. Safe to call between unrelated workloads.

destroyDevice()

destroyDevice(): void

Full teardown: releases every session, including the last one — which destroys the shared GPUDevice. Anything else built on denoiser.device (a three.js renderer, canvas contexts, your own pipelines) dies with it. Only call this when the entire WebGPU stack is going away.

on(event, callback)

on(event: DenoiserEvent, callback: (value: unknown) => void): () => void

DenoiserEvent is 'progress' | 'executed'. Returns an unsubscribe function.

  • 'progress' — same callback signature/semantics as the per-call onProgress option, fired for every call (not just ones that passed their own onProgress). See progress semantics.
  • 'executed' — fired once per completed call (including aborted ones) with the call's result: ImageData | Float32Array | GPUTexture | undefined depending on which method ran.

Progress semantics

onProgress (and the 'progress' event) receive a 0–1 fraction. In tiled mode it's called once per batch of tiles as they complete (incremental progress). In whole-frame mode there is exactly one implicit batch, so it's called exactly once, near the end of the run, with 1 — not incrementally. Don't rely on it for a smooth progress bar on small/whole-frame images; it's meaningful for large tiled runs.

DenoiseStats

Returned by denoiser.stats — per-stage wall-clock timings (ms) from the most recently completed run:

fieldtypemeaning
width, heightnumberthe run's image size
tilesnumbertotal tile count (1 for whole-frame)
batchesnumbernumber of session.run calls
tileW, tileHnumberthe chosen tile geometry
batchSizenumbertiles per batch actually used
uploadMsnumberinput upload + accumulate/weight buffer clear
encodeMsnumberWGSL extract/accumulate encode + submit (CPU-side)
runMsnumbersum of awaited network-run time (NetworkBinding.run()). Runtimes submit GPU work asynchronously (ORT and HF kernels both return in ~1 ms), so this is mostly CPU encode/submit time; the network's GPU time lands in resolveMs, where the readback waits for the queue. Use totalMs to compare runtimes
resolveMsnumberresolve pass + readback/map
totalMsnumberwall clock for the whole call

Errors

Both extend Error with name set to match, so err.name or instanceof both work.

classthrown when
DenoiserUnsupportedErrorWebGPU/device-capability problems: no adapter available (Denoiser.create() with no WebGPU support), or precision:'fp16' requested on a device without shader-f16 (only surfaces directly from the lower-level DenoiseEngine.create() — Denoiser.create() catches this case and falls back to fp32 instead)
DenoiserInputErrorbad/inconsistent inputs: albedo/normal size mismatch vs color, a raw {data,width,height} input whose byte length isn't width*height*4, or a model that requires albedo/normal but didn't get one
Note

An outputTexture/output with the wrong size, missing STORAGE_BINDING, or an unsupported format throws a plain Error, not DenoiserInputError — an inconsistency in the current source (worth catching generically, or catching Error broadly, if you specifically handle DenoiserInputError for input validation).

See Troubleshooting for the exact wording of each thrown/warned message and how to fix the underlying cause.

Types

typeshape
Quality'fast' | 'balanced' | 'high'
ImgInputImageData | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap | OffscreenCanvas | { data: Uint8ClampedArray; width: number; height: number }
OutputTransfer'linear' | 'srgb' | 'aces-srgb'
DenoiserEvent'progress' | 'executed'

Advanced / lower-level exports

These are exported from the package (import { ... } from 'denoiser/ort', no longer from the root entry) but sit below Denoiser — most integrations never need them directly. Documented here for completeness; treat their shapes as more likely to change than the Denoiser surface above.

Models

From denoiser/ort (no longer exported from the root denoiser entry; needs the optional onnxruntime-web peer dependency). Singleton .onnx model loader/cache the ORT runtime uses internally.

membertypemeaning
Models.getInstance()static (): Modelsthe singleton instance
pathstring | undefinedsubdirectory under the site root, used only when url is unset
urlstringremote base URL for models (default: the jsDelivr models-v2 URL — same default as OrtRuntimeOptions.weightsUrl)
precision'fp32' | 'fp16'default 'fp32'; OrtRuntime sets it from the precision of each model it loads
get(name, overrideUrl?)(string, string?) => Promise<Uint8Array>fetch (and cache) a model's raw bytes
has(name)(string) => booleanwhether a model is already cached for the current precision

DenoiseEngine

From denoiser/ort. The WebGPU/ONNX inference core of the ORT runtime (session management, tiling/geometry planning, WGSL pre/post). Denoiser covers everything most integrations need — reach for DenoiseEngine directly only if you're bypassing the high-level model-selection logic.

membertypemeaning
DenoiseEngine.create(modelBytes, options)static (Uint8Array, EngineOptions) => Promise<DenoiseEngine>builds a session for one specific model file. precision:'fp16' without shader-f16 throws DenoiserUnsupportedError here (no auto-fallback — that lives in Denoiser)
deviceGPUDevicethe device this engine (and thus Denoiser) runs on
channelsnumber3 | 6 | 9, must match the loaded model
precision'fp32' | 'fp16' (readonly)
maxRunPixelsnumbermutable after creation
batchnumbermax tiles per run in tiled mode
lastStatsDenoiseStats | undefinedsame shape as Denoiser.stats
graphCapturedbooleanwhether this engine's sessions run with graph capture
planFor(w, h)(number, number) => Planthe whole-frame-vs-tiled geometry the engine would choose for a size
denoise(color, w, h, options?)(Uint8ClampedArray, number, number, DenoiseOptions?) => Promise<Uint8ClampedArray>CPU-array path
denoiseTextures(inputs, options?)(TextureInputs, TextureDenoiseOptions?) => Promise<Uint8ClampedArray | GPUTexture>texture path
tileGrid(w, h)(number, number) => { tilesX: number; tilesY: number }tile grid for a size at the current settings
trim()() => voidwhat Denoiser.dispose() calls
destroy()() => voidwhat Denoiser.destroyDevice() calls

TextureInputs ({ color: GPUTexture; albedo?: GPUTexture; normal?: GPUTexture }) and TextureDenoiseOptions (DenoiseOptions + inputFlipY? + auxInputFlipY?

  • toTexture? + outputTexture?) are the lower-level shapes DenoiseTexturesOptions maps onto.

Network runtimes

Denoiser and TiledEngine do all tiling, transfer functions and WGSL pre/post themselves; a NetworkRuntime only executes the U-Net. The default is AutoRuntime (WebNN fp16 for base/large models when available, hand-written WGSL otherwise; never ORT). Other runtimes (ORT, HF kernels, WebNN, WGSL) plug in through the same interface:

import { Denoiser } from 'denoiser';
import { OrtRuntime } from 'denoiser/ort'; // needs `npm i onnxruntime-web`

const denoiser = await Denoiser.create({
  runtime: new OrtRuntime({ weightsUrl: '/models' }), // or any NetworkRuntime
});
typerole
NetworkRuntimeload(model: NetworkModel, hints?) => Promise<NetworkSession> — fetches and opens a model by OIDN name ({ name, channels, precision }). The first load may create the GPUDevice
NetworkSessiondevice (the engine runs its WGSL here too), optional fixedGeometry, bind(geometry) => Promise<NetworkBinding>, release()
NetworkBindinginput / output GPUBuffers (NCHW [B, C, H, W] / [B, 3, H, W] in model precision) and run(): the engine writes input, awaits run(), reads output
TiledEngine.load(runtime, model, options?)the runtime-agnostic engine Denoiser uses (same members as DenoiseEngine below, minus the ORT-specific ones)
OrtRuntime / OrtSessionthe onnxruntime-web implementation, from denoiser/ort; OrtRuntimeOptions = { weightsUrl?, wasmPaths?, graphCapture?, splitAux? }

Entry points (one package, shared chunks, a single copy of the core):

importcontents
denoiserDenoiser defaulting to AutoRuntime (options tzaUrl, device), plus everything in denoiser/core and AutoRuntime. Never loads onnxruntime-web
denoiser/coreruntime-agnostic Denoiser (requires runtime), TiledEngine, the runtime and temporal types, parseTZA / halfToFloat / DEFAULT_TZA_URL
denoiser/ortOrtRuntime, OrtSession, Models, DenoiseEngine. Needs the optional peer dependency onnxruntime-web. Wrong on the *_alb, *_alb_nrm and *_large models, see Choosing a runtime
denoiser/kernelsexperimental KernelsRuntime on @huggingface/kernels (optional peer dependency, install it to use this entry)
denoiser/wgslWgslRuntime: the U-Net in hand-written WGSL, no dependencies, no flags, correct on every model (the AutoRuntime fallback). Tuned and validated on Apple GPUs
denoiser/autoAutoRuntime (also exported from denoiser): picks WebNN (fp16 base/large, when available) or WGSL per model, on one shared GPUDevice. See Choosing a runtime
denoiser/webnnexperimental WebnnRuntime: the U-Net via MLGraphBuilder (WebNN). Needs Chrome with --enable-features=WebMachineLearningNeuralNetwork
denoiser/ffxFfxShadowDenoiser and FfxReflectionDenoiser, the real-time TemporalDenoisers (WGSL port of AMD FidelityFX Denoiser v1.1.4). No dependencies
denoiser/threethree.js (WebGPURenderer) helpers and TSL nodes: getDevice, getGPUTexture, CameraHistory, createDenoiserForRenderer, DenoiseNode, TemporalDenoiseNode and more. three >= r185 is an optional peer dependency. See three.js & TSL

The classes from denoiser and denoiser/core are the same. The interface is new in this release and may change, so pin your version if you implement it. See Choosing a runtime for measured speed and quality.

Runtime options

All runtimes except ORT read the upstream OIDN .tza weights. AutoRuntime (the default) delegates to WebnnRuntime and WgslRuntime.

runtimeoptions
AutoRuntime{ tzaUrl?, device?, webnn?: 'gpu' | 'npu' | false }. webnn defaults to 'gpu'; false disables WebNN. lastChoice reports the runtime that served the last load; choose(model) reports which one a model would use
OrtRuntime{ weightsUrl?, wasmPaths?, graphCapture?, splitAux? }. weightsUrl is the base URL of the .onnx models (default: jsDelivr models-v2); wasmPaths is where onnxruntime-web loads its wasm (default: jsDelivr, onnxruntime-web@1.27.0); graphCapture (default false) is opt-in ORT WebGPU graph capture, leave off since onnxruntime-web 1.27 crashes captured sessions after about 150-250 cumulative replays; splitAux (default true) works around an onnxruntime-web WebGPU bug on the 9-channel cleanAux models by running the first conv in WGSL, fetching <model>.tail.onnx + <model>.enc0.bin next to the model, and falls back to the plain model with a one-time console warning if those are not hosted
KernelsRuntime{ tzaUrl?, device?, zeroCopy? }. zeroCopy (default true) binds the engine straight to kernels-owned IO tensors
WgslRuntime{ tzaUrl?, device?, profile?, tiling?, subgroupMatrix?, mmaTiling? }. subgroupMatrix ('auto' by default) uses the 8x8 subgroup-matrix conv when the device has chromium-experimental-subgroup-matrix, for fp32 only; profile records per-layer GPU timings (diagnostics, needs timestamp-query); tiling and mmaTiling are tuning overrides
WebnnRuntime{ tzaUrl?, deviceType?, device?, interop?, layout? }. deviceType is 'gpu' (default), 'npu' or 'cpu'; interop is 'auto' by default (WebNN to WebGPU tensor export for fp16 only); layout is 'nchw' (default) or 'nhwc'. WebnnRuntime.isAvailable() reports whether navigator.ml and MLGraphBuilder exist. lastBuildMs holds the graph build time of the most recent bind

tzaUrl defaults to DEFAULT_TZA_URL. device makes the runtime adopt your GPUDevice; without it, the runtime creates one with the adapter's maximum limits and features. OrtRuntime cannot adopt a device: it creates its own.

parseTZA(buf) / DEFAULT_TZA_URL

import { parseTZA, DEFAULT_TZA_URL } from 'denoiser/core';

const buf = await (await fetch(`${DEFAULT_TZA_URL}/rt_hdr_small.tza`)).arrayBuffer();
const tensors = parseTZA(buf); // TensorMap = Map<string, { data: Float32Array; shape: number[] }>
exportmeaning
DEFAULT_TZA_URLbase URL of the upstream OIDN .tza weights (sha256-identical to RenderKit/oidn-weights), on jsDelivr: https://cdn.jsdelivr.net/gh/pmndrs/denoiser-weights@models-v3/tzas
parseTZA(buf: ArrayBuffer)parses an OIDN .tza blob into a TensorMap. fp16 tensors are widened to Float32Array; conv weights stay OIHW. Throws Invalid or corrupted weights blob on a bad magic number and on an unsupported major version
halfToFloat(h: number)IEEE half (as a 16-bit integer) to number
TensorMap, WeightTensorMap<string, WeightTensor>; WeightTensor = { data: Float32Array; shape: number[] }

These exist for runtimes that read OIDN weights directly. You only need them if you write your own NetworkRuntime.

Temporal (real-time) denoising

Exported from denoiser and denoiser/core; the FidelityFX implementations come from denoiser/ffx. See Real-time (temporal) denoising for the frame contract, conventions and measured results.

TemporalDenoiser<Inputs>

interface TemporalDenoiser<Inputs extends object = object> {
  readonly name: string;
  readonly device: GPUDevice;
  configure(config: TemporalConfig): void;
  dispatch(inputs: Inputs, guides: FrameGuides, camera: FrameCamera): GPUTexture;
  resetHistory(): void;
  readonly gpuTimings?: ReadonlyMap<string, number>;
  dispose(): void;
}
membermeaning
configure({ width, height })(re)allocate for a size; drops history. Call before the first dispatch
dispatch(inputs, guides, camera)denoise one frame. Synchronous: records and submits its passes on device.queue, no awaits, no readback. Call it after the frame's inputs are submitted. The returned texture is owned by the denoiser and is valid until the next dispatch / configure / dispose
resetHistory()drop all history on the next dispatch (camera cut, teleport, scene switch)
gpuTimingsper-pass GPU ms when timestamp queries are available (total = sum of passes)
dispose()release GPU resources; never destroys the device

Frame types

typeshape
TemporalConfig{ width: number; height: number }
Mat4Float32Array | ArrayLike<number>, column-major 4x4 (three.js Matrix4.elements)
FrameGuides{ depth: GPUTexture; normal: GPUTexture; motion: GPUTexture; roughness?: GPUTexture; albedo?: GPUTexture }. depth is hardware depth or r32float; normal is world-space in [-1, 1]; motion is the NDC delta current minus previous (rg16float); roughness is perceptual [0, 1]
FrameCamera{ view: Mat4; projection: Mat4; prevView: Mat4; prevProjection: Mat4; jitter?: readonly [number, number]; near: number; far: number; position?: readonly [number, number, number]; reversedDepth?: boolean }. projection is unjittered; jitter is in pixels (default [0, 0]); reversedDepth defaults to false

Signal inputs

typeshape
ShadowInputs{ visibility: GPUTexture; hitDistance?: GPUTexture }. visibility in [0, 1], 1 = lit
SpecularInputs{ radiance: GPUTexture; hitDistance: GPUTexture }. Linear HDR radiance (rgba16float); hit distance r16float, 0 = miss
DiffuseInputs{ radiance: GPUTexture; hitDistance?: GPUTexture }
BeautyInputs{ color: GPUTexture; albedo?: GPUTexture; normal?: GPUTexture; hdr?: boolean }

DiffuseInputs and BeautyInputs are defined for future denoisers; nothing implements them yet.

FfxShadowDenoiser / FfxReflectionDenoiser

import { FfxShadowDenoiser, FfxReflectionDenoiser } from 'denoiser/ffx';

new FfxShadowDenoiser(device: GPUDevice, options?: FfxShadowDenoiserOptions);       // TemporalDenoiser<ShadowInputs>,   name 'ffx-shadows'
new FfxReflectionDenoiser(device: GPUDevice, options?: FfxReflectionDenoiserOptions); // TemporalDenoiser<SpecularInputs>, name 'ffx-reflections'
FfxShadowDenoiserOptionsdefaultmeaning
depthSimilaritySigma1FFX edge-stopping on linear depth, in view units
visibilityThreshold0.5visibility at or above this counts as lit (FFX takes a binary ray-hit mask)
timestampstrueper-pass GPU timestamps into gpuTimings when supported
FfxReflectionDenoiserOptionsdefaultmeaning
temporalStabilityFactor0.7history clip box scale; higher is more stable and more ghosting
roughnessThreshold1pixels with linear roughness at or above this pass through untouched; 1 denoises all pixels
roughnessIsPerceptualtrueguides.roughness is perceptual (squared before use); false for linear
missDistancecamera.farray length used where hitDistance <= 0
currentFrameStatisticstrueread this frame's 8x8 average radiance and sample count; false reproduces FFX SDK v1.1.4 exactly (dark first frame after a reset)
timestampstrueper-pass GPU timestamps into gpuTimings when supported

FfxReflectionDenoiser requires guides.roughness. The shadow dispatch returns an rgba16float texture whose .r is the denoised visibility.

denoiser/three helpers

exportmeaning
getDevice(renderer)the GPUDevice of an initialized WebGPURenderer
getGPUTexture(renderer, texture)the raw GPUTexture behind a three texture (must already be allocated on the GPU)
CameraHistoryupdate(camera, { unjitteredProjection?, jitter? }) => FrameCamera keeps the previous frame's matrices; reset() forgets them

determineModel(selector)

function determineModel(selector: ModelSelector): { name: string; channels: number }

Resolves an OIDN model filename + channel count from a { filterType, quality, hdr, useColor, useAlbedo, useNormal, cleanAux, dirtyAux } selector (ModelSelector) — e.g. { filterType: 'rt', quality: 'fast', hdr: true, useColor: true, useAlbedo: true, useNormal: true, cleanAux: true, dirtyAux: false } → { name: 'rt_hdr_calb_cnrm_small', channels: 9 }. Denoiser calls this internally on every call to pick which model to run; exported for anyone building their own model-selection logic on DenoiseEngine directly.