Skip to content

glitter-gl

An OpenGL geometry, matrix, and shader library for glitter, a Replicant-style GTK4 renderer for Jolt. Ported from glimmer-gl, which does the same for glimmer (glitter's Reagent-style sibling). It does two things:

  • Composable geometry, ported (via glimmer-gl) from thi.ng/geom: build 3D solids as plain Clojure data, transform and combine them, and tessellate to a GL-ready vertex buffer. Vectors, column-major 4×4 matrices, a mesh model, and primitive constructors.
  • Shaders as data (glitter-gl.shader), ported (via glimmer-gl) from thi.ng/geom's shader-spec model: declare a shader's interface (uniforms, attributes, varyings, version) as maps and compose its stages from reusable GLSL snippets; the declarations are generated and only emitted as a GLSL string when you compile.
  • A GL widget for glitter: requiring glitter-gl.gtk registers a :gl-area (a GtkGLArea drawing surface) into glitter's widget registry, so a GL pane lives in the same reconciled hiccup tree as the rest of your UI.

glitter-gl.vector gives unboxed 3D vectors, glitter-gl.matrix gives column-major 4×4 matrices (the layout glUniformMatrix4fv expects) with inlined flonum arithmetic, glitter-gl.gl binds the slice of OpenGL you need to compile shaders and fill buffers/VAOs/uniforms, and glitter-gl.mesh / glitter-gl.primitives are the thi.ng/geom-style composition layer.

orbit: six solids over a lit, shadowed ground plane, driven through glitter-gl.app/reactive-area. Nine more takes, and what each one demonstrates, in the examples gallery.

Docs site: https://jlt-commons.github.io/glitter-gl/

Requirements

  • Jolt: Clojure on Chez Scheme. This project is not JVM Clojure and there is no clojure/lein path.
  • GTK4: glitter renders through it, and :gl-area is a GtkGLArea.
  • A working OpenGL context. macOS ships OpenGL as a framework; on Linux it is the libGL shared object from mesa or your vendor driver. The headless bb check needs no display, but every interactive demo does.
  • babashka (bb) for the task wrappers. Optional: every task has a jolt -M:<alias> equivalent.
brew install gtk4        # macOS

Nothing else needs to sit next to this checkout: deps.edn pins glitter as a git coordinate, so a fresh clone builds on its own. See Dependency modes if you want to develop against a local glitter instead.

Quick start

jolt -M:plasma         # rotating cube/sphere/tetra + composable plasma/stripes shader
jolt -M:ripple         # full-screen fragment shader, no mesh at all
jolt -M:orbit          # several solids orbiting a lit, shadowed ground plane
jolt -M:knot           # a (2,3) torus knot, geometry generated from scratch
jolt -M:gears          # three counter-rotating cog outlines, 2D, orthographic
jolt -M:textured       # rotating cube wearing a procedural checkerboard texture
jolt -M:picking        # pointer-driven ray picking against a ground and a wall
jolt -M:check          # headless sanity check: shader compiles, geometry buffers valid
jolt -M:test           # unit suite
jolt -M:gl-area-smoke  # live-GTK smoke: :gl-area construct/realize/render/resize

In CI, invoke the alias form (jolt -M:test, not jolt test). On every released jolt the task shorthand swallows the child's exit status, so a failing suite reports green. Fixed on jolt main after v0.7.27 but not yet in a tagged release, so the alias form stays the safe default. See docs/guide/testing-and-tasks.md for the probe.

Or via bb

bb info      # start here: grouped task list
bb test      # jolt -M:test
bb plasma    # interactive demo
bb ripple    # interactive demo, no mesh
bb orbit     # interactive demo, via app/reactive-area
bb knot      # interactive demo, generated geometry
bb check     # headless sanity check
bb smokes    # live-GTK smoke, CI-safe
bb hooks:install / :install:full / :uninstall  # git pre-commit hook: fast | +tests | remove

bb hooks:install sets up a fast pre-commit hook (lint errors + format + ns cleanliness) that gates every commit on staying bb lsp:format-check- clean: the whole codebase is formatted uniformly, including examples/ and the 22 files ported verbatim from glimmer-gl. See CONTRIBUTING.md's invariant #1 for why a project-wide clojure-lsp format pass doesn't conflict with the "don't improve ported files" porting discipline. It mirrors glitter's identical resolution of the same tension for its own Replicant-ported files.

Dependency modes

deps.edn declares glitter as a pinned git coordinate (io.github.jlt-commons/glitter at a fixed :git/sha). jolt fetches and builds against that exact commit, so a fresh clone of this repo builds with no other setup. This is the default, and what every command above uses:

jolt -M:plasma           # builds against the pinned glitter sha

To co-develop this library against an unreleased glitter change, the :dev alias overrides the pin back to a sibling checkout at ../glitter. Combine it with any runnable alias:

jolt -M:dev:plasma       # builds against ../glitter instead of the pin
jolt -M:dev:test

:dev only helps if ../glitter actually exists next to this checkout. It is not something a first-time user needs or has.

Bumping the pin is deliberate: change the :git/sha in deps.edn, then run bb test and bb smokes before committing.

Architecture

flowchart TD
  subgraph pure["Pure library: no glitter dependency, usable from any Jolt + OpenGL program"]
    geom["Geometry and math (14 files)<br/>vector, vec2, matrix, quaternion<br/>aabb, rect, circle, line, plane<br/>triangle, sphere, polygon<br/>bezier, intersect"]
    mesh["Mesh model (4 files)<br/>mesh, glmesh<br/>primitives, polyhedra"]
    glplumb["GL plumbing (5 files)<br/>shader, gl, ffi-compat<br/>offscreen, renderer"]
    scene["scene.clj<br/>scene tree to render plan<br/>(requires glitter-gl.matrix only)"]
  end

  subgraph bridge["glitter integration"]
    gtk["gtk.clj<br/>registers the :gl-area widget<br/>handlers wire from :apply,<br/>never :connect"]
    app["app.clj<br/>reactive-area builds the<br/>:gl-area prop map bound to<br/>glitter's state atom"]
  end

  glitter["glitter<br/>glitter.ffi, glitter.widget"]

  geom --> mesh
  mesh --> glplumb
  geom --> scene
  glplumb --> app
  scene --> app
  gtk --> app
  glitter -.->|"required by gtk.clj alone,<br/>1 of 25 files in src/glitter_gl"| gtk
Loading
  • glitter-gl.gtk registers :gl-area into glitter's widget registry. The imperative realize/render/resize/tick/motion/key/button signals are wired from the widget spec's :apply closure (called once per prop key at construction, and again on every re-render), guarded so each event only ever connects once per widget. This is a real, live-found correction to the original design, which called for wiring them once at construction via glitter.widget's :connect hook. :connect never actually sees a hiccup element's real props under glitter's reconcile flow, so it silently never fired. See docs/guide/gl-area-widget-layer.md for the full story.
  • glitter-gl.scene/glitter-gl.app do NOT track reactive-cell dependencies the way glimmer-gl's originals do: glitter's own state-atom watcher already recomputes the whole view on every change, so a GL scene built the same way (a pure function of state) needs no separate tracking layer.

Documentation

The full guide is nine pages under docs/guide/, one topic per page:

  • architecture.md: the four-layer stack, how thin the seam to glitter actually is (one file has a literal :require on glitter.*), and why a :gl-area keeps redrawing even though a bare state change doesn't cause it to.
  • geometry-and-shaders.md: orientation over the 22 verbatim-ported namespaces. The three groups, where a mesh becomes GL data, and why the matrices are column-major.
  • gl-area-widget-layer.md: the :gl-area widget's mechanics in full. The :apply-vs-:connect correction, traced through the actual reconciler code path, plus every non-standard GTK4 signal shape it has to handle.
  • scene-and-app.md: the declarative scene graph's mini-hiccup dialect (and why it's not glitter's own hiccup), why plan has no reactive-cell tracking, and the write-once handler contract.
  • porting-and-attribution.md: the three sourcing buckets, the Standard Verbatim Port Procedure, and the lineage back through glimmer-gl to thi.ng/geom.
  • examples.md: what each of the ten examples/glitter_gl/ namespaces is for, and which two are actually wired into regression coverage. Includes a four-take gallery of the plasma demo, plus one take each of ripple, orbit, knot, gears, textured and picking.
  • testing-and-tasks.md: the unit suite, the live-GTK smoke and headless check bb smokes runs, and the quality-tooling task surface (lint, format, clj-kondo's FFI hook, the git pre-commit gate).
  • limitations.md: every known v1 gap, each with the reason it was left rather than fixed.
  • index.md: the guide's own nav map, if you'd rather start there.

Plus:

  • CONTRIBUTING.md: how to build, test, and submit changes, and the ten invariants this project does not regress.
  • NOTICE.md: file-by-file attribution for the ported code.
  • Design spec and implementation plan are not part of this repo; they live in a private planning store.

Contributing

See CONTRIBUTING.md for setup, the ten numbered invariants this project does not regress, and how to add a widget. Before opening a PR, run the four local gates: bb test, bb lint, bb lsp:format-check, and bb smokes. CI runs the first three plus bb lsp:clean-ns-check on every PR, and a second job exercises real OpenGL under Xvfb on mesa's llvmpipe. Both can fail the build. bb smokes is still worth running yourself, since software rendering is not your driver.

Status

Ported from glimmer-gl (2026-08-06 arc). See NOTICE.md's porting ledger for the full verbatim/adapted/new breakdown, including a real live-found correction to :gl-area's original wiring design (see docs/guide/gl-area-widget-layer.md). The unit suite currently stands at 178 tests / 559 assertions (bb test), plus bb smokes' live-GTK smoke (gl-area-smoke, which drives a real GtkGLArea under the real reconciler rather than a fake renderer) and its headless check (check, which needs no GL context or display at all). glitter-gl.app's scene-graph/shadow-mapping renderer path (reactive-area) is ported, unit-tested, and, as of this arc, exercised live end to end by examples/glitter_gl/orbit.clj. The plasma demo still wires :gl-area directly, the same way glimmer-gl's own upstream demo does; ripple, knot, gears, textured and picking do too, for reasons of their own (see NOTICE.md). See docs/guide/limitations.md for this and every other known v1 gap.

Licence

Copyright (c) 2026 Burin Choomnuan.

glitter-gl's own code is distributed under the Eclipse Public License 2.0, matching the rest of jlt-commons and jolt itself. SPDX identifier: EPL-2.0. It was Apache License 2.0 until 2026-09-05.

The geometry, matrix, mesh and shader layer is ported from glimmer-gl, and through it from thi.ng/geom, which Karsten Schmidt licenses under the Apache License 2.0. Those files keep that licence and its attribution requirements, and NOTICE.md is the file-by-file record that carries the required notice. EPL 2.0 relicenses none of it: the whole is EPL 2.0 and each part keeps what it came with, as long as that notice travels with it.

About

An OpenGL geometry, matrix and shader library for glitter: composable meshes, shaders as data, and a :gl-area widget for GTK4. Native Clojure on Chez Scheme, no JVM.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages