nanoraster
0.4.1

Handle render failures

Classify a rejection, decide whether retrying can help, and keep the last good image when a GPU fault is transient.

Open Markdown

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 ones worth retrying.

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;
    }
  }
};

Retrying a parse failure loops forever, so the guard checks the class before the attempt count.

4. Keep the last good image

A transient GPU fault should not blank 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 there is no adapter, 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 round out the answer for a bug report. In browsers deviceType is 'unknown' unless the adapter declares itself a fallback: WebGPU withholds the class, and a browser with only software support usually returns no adapter at all.

Variations

Distinguishing bad GLB from bad options. Both surface as parse, since both violate the input contract; message names which.

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. 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. Every code and its retry guidance is listed under RenderError.

On this page