Handle render failures
Classify a rejection, decide whether retrying can help, and keep the last good image when a GPU fault is transient.
Turn a rejected render into a decision: fix the input, retry, or fall back.
1. Normalise whatever was thrown
RenderError.from never throws, so it is safe as the first line of a catch.
import { RenderError, renderImage } from 'nanoraster';
try {
const image = await renderImage(glb, { format: 'webp' });
console.log(image.name);
} catch (error) {
const failure = RenderError.from(error);
console.error(failure.code, failure.message);
}2. Split transient faults from input faults
isGpuFault is true for the three codes that describe the device rather than
the request, the only codes worth a retry.
try {
await renderImage(glb, { format: 'webp' });
} catch (error) {
const failure = RenderError.from(error);
if (!failure.isGpuFault) {
throw failure; // driver-unsupported, parse, encode, unknown — retrying changes nothing
}
// adapter-unavailable, device-lost, gpu — worth another attempt
}3. Retry only the transient class
import { RenderError, renderImage, type RenderedImageFile } from 'nanoraster';
const renderWithRetry = async (glb: Uint8Array<ArrayBuffer>, attempts = 3): Promise<RenderedImageFile> => {
for (let attempt = 1; ; attempt += 1) {
try {
return await renderImage(glb, { format: 'webp' });
} catch (error) {
const failure = RenderError.from(error);
if (!failure.isGpuFault || attempt >= attempts) throw failure;
}
}
};The class check comes first: retrying a parse failure never ends.
4. Keep the last good image
let lastGood: RenderedImageFile | undefined;
try {
lastGood = await renderImage(glb, { format: 'webp' });
} catch (error) {
const failure = RenderError.from(error);
if (!failure.isGpuFault) throw failure;
// lastGood still holds the previous render
}5. Tell a missing GPU from a slow one
adapter-unavailable means no adapter at all. A host can also succeed with a
software adapter (SwiftShader, lavapipe, WARP) that renders correctly but an
order of magnitude slower. describeAdapter is the preflight that separates
the two before you commit to a workload. It resolves undefined when no adapter
exists, rather than rejecting:
import { describeAdapter } from 'nanoraster';
const adapter = await describeAdapter();
if (!adapter) {
// Nothing will render here: fall back now.
} else if (adapter.deviceType === 'cpu') {
// Renders work; budget ~10x the usual latency or reduce sizes.
}backend and name complete the answer for a bug report. In browsers,
deviceType is 'unknown' unless the adapter declares itself a fallback,
because WebGPU withholds the class. A browser with only software support
usually returns no adapter at all.
Variations
Distinguishing bad GLB from bad options. Both surface as parse: both violate
the input contract. message names which.
Triangles without NORMAL. These render with glTF's implicit flat normals,
ignoring TANGENT; author normals for smooth curves.
A disposed renderer. Calls on a disposed Renderer
reject with code gpu and the message gpu: renderer disposed. Unlike other
GPU-class faults, this one is permanent. Recreate the renderer; do not retry
the call.
JPEG with a transparent background. This fails as encode, not parse:
the request is well-formed, but the encoder cannot represent it. See
Format and annotate.
Unknown failures. unknown means the thrown value carried no recognised
tag; do not retry. RenderError lists every code and
its retry guidance.