OpenGL geometry and shaders, as plain Clojure data.

glitter-gl builds solids out of vectors and matrices, and shader programs out of maps describing their uniforms and statements: ordinary Clojure data, tessellated to a GL vertex buffer or emitted as GLSL only when it actually has to run. It's written for Jolt, native Clojure on Chez Scheme: no JVM, no boxing, just OpenGL and your data.

A sphere rotating under a composable plasma and gold-stripe shader; its high facet count makes the color read as a continuous gradient instead of flat panels

The plasma demo: a domain-warped plasma shader blended with animated gold stripes, composed from two independent shader modules and lit with a simple diffuse term, wired straight to :gl-area. The guide has three more takes on the same shader, including flat shading on a cube and a tetrahedron.

The seam to glitter is exactly one file

flowchart LR
  subgraph pure["24 files: no dependency on glitter at all"]
    geom["vector · vec2 · matrix · quaternion
aabb · rect · circle · line · plane
triangle · sphere · polygon
bezier · intersect"] mesh["mesh · glmesh
primitives · polyhedra"] glplumb["shader · gl
offscreen · renderer"] scene["scene.clj
scene tree to render plan"] app["app.clj
reactive-area builds the
:gl-area prop map"] end gtk["gtk.clj
the one file"] glitter["glitter
widget registry"] geom --> mesh mesh --> glplumb geom --> scene glplumb --> app scene --> app gtk --> app glitter -.->|"required by gtk.clj alone,
1 of 25 namespaces"| gtk

Of the 25 namespaces under src/glitter_gl/, only gtk.clj has a literal :require on glitter.*. The geometry, matrix, mesh, and shader halves run from any Jolt program that has an OpenGL context: pull in glitter-gl.gtk only if you also want a :gl-area pane in a glitter UI.

Shaders as data

A shader is a map declaring its interface: uniforms, attributes, varyings, and a GLSL version. Bodies are vectors of statements built from expression nodes, and independent modules merge with ordinary map operations before anything touches the GPU:

{:version  "330 core"
 :uniforms {:u_mvp :mat4, :u_time [:float 0.0]}    ; type, or [type default]
 :attribs  {:a_pos [:vec3 0], :a_normal [:vec3 1]} ; [type location]
 :varying  {:v_normal :vec3}                       ; out in vs, in in fs
 :fs-out   {:frag_color :vec4}
 :vs-main  [[:set :gl_Position [:* :u_mvp [:vec4 :a_pos 1.0]]]]
 :fs-main  [[:let :n :vec3 [:normalize :v_normal]] ...]}

The uniform/in/out declarations are generated from the maps, and merge-specs combines modules (a plasma term, a stripe term, a lighting term) into one program. The GLSL string is only produced when program compiles it.

What's here

Geometry as data

Solids are built from glitter-gl.primitives (cuboid, tetrahedron, sphere, plane) as plain Clojure meshes, transformed and combined with ordinary functions, then tessellated to an interleaved position+normal float buffer for the GPU.

A GL pane in your UI tree

Requiring glitter-gl.gtk registers a :gl-area tag into glitter's widget registry, so a GtkGLArea reconciles in the same hiccup tree as every other widget.

Ported where it counts

22 of 25 namespaces are verbatim ports (geometry, matrices, meshes, the shader DSL, raw GL bindings) via glimmer-gl from thi.ng/geom; 3 are adapted for glitter's single state atom.

Tested against real GL

177 tests, 556 assertions, plus a live-GTK smoke that drives a real GtkGLArea under the actual reconciler rather than a fake renderer.

Quickstart

jolt -M:plasma         # rotating cube/sphere/tetra + composable plasma/stripes shader
jolt -M:test           # unit suite: 177 tests, 556 assertions
jolt -M:check          # headless sanity check: shader compiles, geometry buffers valid
jolt -M:gl-area-smoke  # live-GTK smoke: :gl-area construct/realize/render/resize

Requires Jolt (tested against v0.7.27), GTK4, and a native OpenGL context. Released under Apache 2.0. Ported from glimmer-gl, which does the same for glitter's Reagent-style sibling glimmer, and which itself ports thi.ng/geom. See the guide for the full nine-page tour.