REGL in Observable Notebooks 2.0

This notebook wires up a simple library function for managing a regl canvas in Observable Notebooks 2.0. It works in Observable Desktop as well as with Notebook Kit.

The main points are:

  1. Dependency injection using regl from jsdelivr.net
  2. Two-phased construction: first create a canvas, then inject size dependencies

To avoid recreating WebGL contexts, I previously used the this variable of Observable JavaScript to access the previous cell return value, but this leans heavily into statefullness within Observable’s stateless evaluation model, and more critically, I don’t actually know how to accomplish this in Notebook 2.0’s Vanilla JavaScript model. This is probably for the best.

So, we’re left with a simple workaround. First, create a canvas with no size, then obtain the final regl instance as we inject the canvas dimension dependencies via .attachResize(). Note that this is a two-step process like the following, so that the final regl instance is used after the size is applied.

If we did the following, we would fine that the execution order is indeterminate and we don’t always operate on a properly resized canvas.

Is it really worth this trouble to avoid creating extra WebGL contexts? Well, yes. Without this, and especially if you have more than one context, you can expect lost contexts and blank diagrams as the number of window resize events exceeds the maximum number of WebGL contexts, typically sixteen.

One other small thing I learned along the way is that, for somewhat obvious security reasons, Observable Desktop cannot import files from parent directories. This limits the ability to have notebooks categorized within directories import library functions. A simple workaround? Symbolic links. In particular, I added a symbolic link called lib within the notebook directory which trivially points to ../lib. This allows both Observable Desktop as well as the Vite-based preview and build scripts to correctly resolve the import.

import { reglCanvas } from './lib/regl-canvas.js'
import createREGL from 'npm:regl@2.1.1'
// Instantiate the context without a size
const _regl = view(reglCanvas(createREGL, {pixelRatio: 1}))
// Pass the context through the resize function
const regl = _regl.attachResize(width, width * 0.3)
// Start using the context
regl.poll();
regl.clear({color: [0.8, 0.9, 1, 1]});