# webgl-common — the shared `gl` package for the WebGL demos

Every `examples/webgl-*` demo used to repeat the same block of host-import
directives for the WebGL2 API, the same WebGL enum constants and the same
shader-compilation helpers — and every page repeated the matching block of
JavaScript bindings. That boundary is now declared once, in `gl.wit`: `gl.lisp`
binds it for the Lisp side, `gl-imports.js` is generated from it for the page
side. A demo splices the package in at compile time with

```lisp
(require :gl "../webgl-common/gl.lisp")
```

and then talks to WebGL through the package:

```lisp
(let ((program (gl:build-program +vertex-shader-source+ +fragment-shader-source+)))
  (gl:use-program program)
  (gl:enable gl:+depth-test+)
  ...
  (gl:draw-arrays gl:+triangles+ 0 36))
```

## What's in here

| File            | Purpose                                                                |
| --------------- | ---------------------------------------------------------------------- |
| `gl.wit`        | The boundary itself: `interface gl` (29 WebGL2 functions) + `interface ui` (`fail`). |
| `gl.lisp`       | The `gl` package: two `rontolisp:wit-import` directives, the enum constants, the shader helpers. |
| `gl-imports.js` | The page's half, GENERATED from `gl.wit` — do not edit.                |

## The boundary is `gl.wit`

`gl.lisp` no longer spells the WebGL2 API out. Two directives bind the two
interfaces of `gl.wit`:

```lisp
(rontolisp:wit-import "gl.wit" :interface "local:webgl/gl")
(rontolisp:wit-import "gl.wit" :interface "local:webgl/ui")
```

Each WIT function lowers into exactly the Preview 1 host import a hand-written
`rontolisp:wasm-import` would have declared, so the compiled module is
unchanged. The directives bind into the *current* package rather than naming
one, so under `(in-package gl)` the bindings land beside the constants and
helpers below — which is why the `defpackage` stays hand-written: it has to
export the constants and helpers too, and no directive knows about those.

The page's import object is generated from the same file
(`gl-imports.js`, regenerated by `GlImportObjectTest`), and each page spreads it
in:

```js
import { glImports, uiImports } from "../webgl-common/gl-imports.js";

const imports = {
  gl: { ...glImports({ gl: gl2, handles, addHandle, str, retStr }),
        setVertex: (i, x, y) => { /* the demo's own staging entries */ } },
  ui: uiImports({ ui, str }),
};
```

So the two sides of the boundary cannot drift: a page can no longer provide a
field the module does not import, or spell one differently, without `gl.wit`
saying so.

## What it exports

- **The WebGL2 API** — `gl:create-shader`, `gl:shader-source`, ...
  `gl:draw-arrays`: the 29 functions of `gl.wit`'s `gl` interface, the union of
  every literal WebGL2 entry the demos use. GL objects cross the boundary as
  `s32` handles into a table the page keeps (named through `gl.wit`'s `shader`,
  `program`, `buffer`, `vertex-array` and `uniform-location` aliases, which are
  plain aliases of `s32`); strings (GLSL source, uniform names, info logs) cross
  as `string`.
- **The WebGL enum constants** — `gl:+vertex-shader+`, `gl:+float+`,
  `gl:+color-buffer-bit+`, ... — hand-written in `gl.lisp`.
- **Shader helpers** — `(gl:make-shader type source)` compiles one shader and
  `(gl:build-program vs fs)` links a whole program, both reporting compile/link
  errors through `gl.wit`'s `ui` interface (`fail`, which shows the page's error
  box and throws). Also hand-written in `gl.lisp`.

Each demo keeps its own page-specific staging imports (`setVertex`, `setFloat`,
...) declared with `rontolisp:wasm-import` next to its own code — those are not
part of the WebGL2 API, their shapes differ per demo, and they deliberately stay
off the WIT.

## A note on the names

A WIT label is ONE name: it becomes the Lisp name verbatim (`create-shader` →
`gl:create-shader`) and, camelCased, the host import field (`createShader`) —
which is what the WebGL2 API already calls itself. The old directives could give
each side a different name with `:as`; a WIT label cannot. Four functions were
therefore renamed to their WebGL spellings, and code written against the old
names needs updating:

| Old (`gl.lisp` before) | Now                     |
| ---------------------- | ----------------------- |
| `gl:shader-compiled-p` | `gl:get-shader-parameter` |
| `gl:shader-info-log`   | `gl:get-shader-info-log`  |
| `gl:program-linked-p`  | `gl:get-program-parameter` |
| `gl:program-info-log`  | `gl:get-program-info-log`  |

The names are the WebGL ones rather than Lisp-idiomatic predicates because
`get-shader-parameter` cannot also be spelled `shader-compiled-p` without giving
the boundary two names to keep in sync. A package qualifier never leaks into the
page's import object.

## Why importing the union is free

The demos compile with `--optimize`, and the tree-shaker drops unused host
imports. So although `gl.wit` declares every entry any demo needs, each compiled
`.wasm` imports only what that demo reaches — between 26 and 44 functions,
staging and canvas entries included.

One nuance: a program that takes functions as values — e.g. through the spliced
`linalg` library — keeps the same-arity import wrappers reachable through the
`funcall` dispatcher, so a couple of entries the demo never calls can survive
the shake. No page has to know: every page provides the whole generated union by
spreading `glImports`, so a surviving entry is already there.

`examples/browser/webgl-triangle` deliberately does not use this package: it is
the smallest complete `rontolisp:wasm-import` program, and staying a single
self-contained file is the point.
