Skip to content

Image viewer: WebGL renderer is rebuilt on every exposure change and never force-loses its context #226

Description

@adulbrich

Status: unverified risk, filed for investigation

This is a code-reading finding, not a reproduced bug. It is filed because it has the same shape as the failure that was reported in #199 (now closed as obsolete — the component that issue described was deleted, but three.js survives in the image viewer).

The pattern

src/app/image-viewer/view/page.tsx:670-754:

useEffect(() => {
  const canvas = canvasRef.current;
  if (!canvas) { return; }
  let renderer: WebGLRenderer | null = null;
  ...
  const dispose = () => {
    legendTexture?.dispose(); legendMaterial?.dispose(); legendGeometry?.dispose();
    material?.dispose(); geometry?.dispose();
    renderer?.dispose();
  };
  renderer = new WebGLRenderer({ antialias: false, canvas });
  ...
  renderer.render(scene, camera);
  return dispose;
}, [activeViewLayers, viewerData.imageHeight, viewerData.imageWidth]);

Three observations:

  1. The renderer is rebuilt on every exposure tick. activeViewLayers is a fresh object on each render (page.tsx:548-568, a useMemo keyed on exposureEv), so dragging the exposure slider tears down and reconstructs the whole WebGLRenderer per 0.1 EV step. Within a single file this is churn rather than exhaustion, since the <canvas> element is stable and getContext returns the existing context.

  2. renderer.dispose() does not release the drawing-buffer context. There is no forceContextLoss() call anywhere in src/. Combined with key={viewerData.texture.uuid} on <TransformWrapper> at page.tsx:799 — which remounts the subtree, and therefore the canvas, whenever a different .hdr is opened via the filePath query param — repeatedly opening images in one session could accumulate orphaned WebGL contexts and hit the browser's context cap (~16), at which point the oldest is force-lost.

  3. The render is one-shot, not a loop. renderer.render at :751 with no requestAnimationFrame. So a lost context would leave a permanently blank canvas with no self-recovery — which is exactly the "the app has to be closed and relaunched" symptom described in Fisheye info derivation feature in app: investigate derive fisheye info bug described in this issue #199.

How to confirm or dismiss

Open a dozen or more different .hdr files in the viewer in one session, without restarting the app, and watch for a canvas that goes blank and stays blank. The browser console should log a WebGL context-loss warning if this is what is happening.

If confirmed

  • Add renderer.forceContextLoss() to the dispose path.
  • Split the effect so exposure changes update the existing material uniform rather than rebuilding the renderer. The exposure already flows through a single scalar (page.tsx:720, material.color.setScalar(activeViewLayers.exposureScale)), so it does not need a renderer teardown at all.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions